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<QObject>(receiver)](
- QString, QSize, QImage img, QString) {
- if (!recv)
- return;
+ MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
+ QSize(size, size),
+ [callback, cacheKey, recv = QPointer<QObject>(receiver)](
+ QString, QSize, QImage img, QString) {
+ if (!recv)
+ return;
- auto proxy = std::make_shared<AvatarProxy>();
- 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<AvatarProxy>();
+ 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<void(QPixmap)>;
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<Cache> 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<class T>
bool
containsStateUpdates(const T &e)
{
- return std::visit([](const auto &ev) { return Cache::isStateEvent_<decltype(ev)>; }, e);
+ return std::visit([](const auto &ev) { return Cache::isStateEvent_<decltype(ev)>; }, 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<StrippedEvent<state::Avatar>>(e) ||
- std::holds_alternative<StrippedEvent<CanonicalAlias>>(e) ||
- std::holds_alternative<StrippedEvent<Name>>(e) ||
- std::holds_alternative<StrippedEvent<Member>>(e) ||
- std::holds_alternative<StrippedEvent<Topic>>(e);
+ return std::holds_alternative<StrippedEvent<state::Avatar>>(e) ||
+ std::holds_alternative<StrippedEvent<CanonicalAlias>>(e) ||
+ std::holds_alternative<StrippedEvent<Name>>(e) ||
+ std::holds_alternative<StrippedEvent<Member>>(e) ||
+ std::holds_alternative<StrippedEvent<Topic>>(e);
}
bool
@@ -153,45 +153,45 @@ Cache::isHiddenEvent(lmdb::txn &txn,
mtx::events::collections::TimelineEvents e,
const std::string &room_id)
{
- using namespace mtx::events;
-
- // Always hide edits
- if (mtx::accessors::relations(e).replaces())
- return true;
-
- if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) {
- MegolmSessionIndex index;
- index.room_id = room_id;
- index.session_id = encryptedEvent->content.session_id;
- index.sender_key = encryptedEvent->content.sender_key;
+ using namespace mtx::events;
- 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<mtx::events::AccountDataEvent<
- mtx::events::account_data::nheko_extensions::HiddenEvents>>(*temp)
- .content);
- if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id))
- hiddenEvents =
- std::move(std::get<mtx::events::AccountDataEvent<
- mtx::events::account_data::nheko_extensions::HiddenEvents>>(*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<EncryptedEvent<msg::Encrypted>>(&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<mtx::events::AccountDataEvent<
+ mtx::events::account_data::nheko_extensions::HiddenEvents>>(*temp)
+ .content);
+ if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id))
+ hiddenEvents =
+ std::move(std::get<mtx::events::AccountDataEvent<
+ mtx::events::account_data::nheko_extensions::HiddenEvents>>(*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);
+
+ // 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);
- databaseReady_ = true;
+ 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<mtx::events::state::Encryption>
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<Encryption> 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<Encryption> 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<InboundSessionObject>(std::string(value), pickle_secret_);
+ auto saved_session = unpickle<InboundSessionObject>(std::string(value), pickle_secret_);
- try {
- index = nlohmann::json::parse(key).get<MegolmSessionIndex>();
- } catch (const nlohmann::json::exception &e) {
- nhlog::db()->critical("failed to export megolm session: {}", e.what());
- continue;
- }
+ try {
+ index = nlohmann::json::parse(key).get<MegolmSessionIndex>();
+ } 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<InboundSessionObject>(session.get(), pickle_secret_);
+ using namespace mtx::crypto;
+ const auto key = json(index).dump();
+ const auto pickled = pickle<InboundSessionObject>(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<InboundSessionObject>(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<InboundSessionObject>(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<InboundSessionObject>(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<InboundSessionObject>(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<OutboundSessionObject>(ptr.get(), pickle_secret_);
+ // Save the updated pickled data for the session.
+ json j;
+ j["session"] = pickle<OutboundSessionObject>(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<OutboundSessionObject>(session.get(), pickle_secret_);
+ using namespace mtx::crypto;
+ const auto pickled = pickle<OutboundSessionObject>(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<OutboundSessionObject>(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<OutboundSessionObject>(obj.at("session"), pickle_secret_);
- if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
- ref.data = nlohmann::json::parse(value).get<GroupSessionData>();
- }
+ 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<GroupSessionData>();
}
+
+ return ref;
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
+ return {};
+ }
}
std::optional<GroupSessionData>
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<GroupSessionData>();
- }
+ 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<GroupSessionData>();
}
+
+ 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<SessionObject>(session.get(), pickle_secret_);
- const auto session_id = mtx::crypto::session_id(session.get());
+ const auto pickled = pickle<SessionObject>(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<mtx::crypto::OlmSessionPtr>
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<StoredOlmSession>();
- return unpickle<SessionObject>(data.pickled_session, pickle_secret_);
- }
+ if (found) {
+ auto data = json::parse(pickled).get<StoredOlmSession>();
+ return unpickle<SessionObject>(data.pickled_session, pickle_secret_);
+ }
- return std::nullopt;
+ return std::nullopt;
}
std::optional<mtx::crypto::OlmSessionPtr>
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<StoredOlmSession> currentNewest;
+ std::optional<StoredOlmSession> currentNewest;
- auto cursor = lmdb::cursor::open(txn, db);
- while (cursor.get(session_id, pickled_session, MDB_NEXT)) {
- auto data = json::parse(pickled_session).get<StoredOlmSession>();
- 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<StoredOlmSession>();
+ if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts)
+ currentNewest = data;
+ }
+ cursor.close();
- txn.commit();
+ txn.commit();
- return currentNewest ? std::optional(unpickle<SessionObject>(currentNewest->pickled_session,
- pickle_secret_))
- : std::nullopt;
+ return currentNewest ? std::optional(unpickle<SessionObject>(currentNewest->pickled_session,
+ pickle_secret_))
+ : std::nullopt;
}
std::vector<std::string>
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<std::string> res;
+ std::string_view session_id, unused;
+ std::vector<std::string> 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<OnlineBackupVersion>
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<OnlineBackupVersion>();
- } catch (...) {
- return std::nullopt;
- }
+ return nlohmann::json::parse(v).get<OnlineBackupVersion>();
+ } 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<std::string>
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<std::pair<std::string, std::function<bool()>>> 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<std::pair<std::string, std::function<bool()>>> 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<std::string>();
- 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<std::string>();
+ 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<std::pair<std::string, StoredOlmSession>> 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<std::pair<std::string, StoredOlmSession>> 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<std::map<std::string, uint64_t>>();
+ 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<std::map<std::string, uint64_t>>();
- } 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<std::string, uint64_t> saved_receipts;
+ std::map<std::string, uint64_t> 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<std::map<std::string, uint64_t>>();
- }
+ // Retrieve the saved receipts.
+ saved_receipts = json_value.get<std::map<std::string, uint64_t>>();
+ }
- // 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<QString, bool> readStatus;
+ std::map<QString, bool> 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<mtx::events::account_data::FullyRead>>(
- &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<mtx::events::AccountDataEvent<mtx::events::account_data::FullyRead>>(
+ &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<std::string>(), j.dump());
- },
- ev);
- }
+ setNextBatchToken(txn, res.next_batch);
- auto userKeyCacheDb = getUserKeysDb(txn);
-
- std::set<std::string> spaces_with_updates;
- std::set<std::string> 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<StateEvent<state::space::Child>>(e) ||
- std::holds_alternative<StateEvent<state::PowerLevels>>(e))
- space_updates = true;
- for (const auto &e : room.second.timeline.events)
- if (std::holds_alternative<StateEvent<state::space::Child>>(e) ||
- std::holds_alternative<StateEvent<state::PowerLevels>>(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<std::string>(), j.dump());
+ },
+ ev);
+ }
- {
- bool room_has_space_update = false;
- for (const auto &e : room.second.state.events) {
- if (auto se = std::get_if<StateEvent<state::space::Parent>>(&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<StateEvent<state::space::Parent>>(&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<std::string> spaces_with_updates;
+ std::set<std::string> 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<std::string>(), j.dump());
- },
- evt);
-
- // for tag events
- if (std::holds_alternative<AccountDataEvent<account_data::Tags>>(
- evt)) {
- auto tags_evt =
- std::get<AccountDataEvent<account_data::Tags>>(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<
- mtx::events::account_data::FullyRead>>(&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<StateEvent<state::space::Child>>(e) ||
+ std::holds_alternative<StateEvent<state::PowerLevels>>(e))
+ space_updates = true;
+ for (const auto &e : room.second.timeline.events)
+ if (std::holds_alternative<StateEvent<state::space::Child>>(e) ||
+ std::holds_alternative<StateEvent<state::PowerLevels>>(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<StateEvent<state::space::Parent>>(&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<StateEvent<state::space::Parent>>(&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<std::string>(), j.dump());
+ },
+ evt);
+
+ // for tag events
+ if (std::holds_alternative<AccountDataEvent<account_data::Tags>>(evt)) {
+ auto tags_evt = std::get<AccountDataEvent<account_data::Tags>>(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<mtx::events::account_data::FullyRead>>(&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<mtx::events::ephemeral::Receipt>>(&e)) {
- Receipts receipts;
+ for (const auto &e : room.second.ephemeral.events) {
+ if (auto receiptsEv =
+ std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(&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<QString, bool> 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<mtx::events::ephemeral::Receipt>>(&e)) {
- std::vector<QString> 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<QString, bool> 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<mtx::events::ephemeral::Receipt>>(&e)) {
+ std::vector<QString> 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<std::string, mtx::responses::InvitedRoom> &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<StrippedEvent<Member>>(&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<StrippedEvent<Member>>(&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<std::string>(), 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<std::string>(), 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<mtx::events::Event<mtx::events::presence::Presence>> &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<std::string>
Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
{
- std::vector<std::string> 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<std::string> 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<QString, RoomInfo>
Cache::getRoomInfo(const std::vector<std::string> &rooms)
{
- std::map<QString, RoomInfo> room_info;
+ std::map<QString, RoomInfo> 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<QString>
Cache::roomIds()
{
- auto txn = ro_txn(env_);
+ auto txn = ro_txn(env_);
- std::vector<QString> rooms;
- std::string_view room_id, unused;
+ std::vector<QString> 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<QString, mtx::responses::Notifications>
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<QString, mtx::responses::Notifications> notifs;
+ QMap<QString, mtx::responses::Notifications> 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<uint64_t>::max()) {
- if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) {
- index = lmdb::from_sv<uint64_t>(indexVal);
- } else {
- messages.end_of_cache = true;
- return messages;
- }
+ auto cursor = lmdb::cursor::open(txn, orderDb);
+ if (index == std::numeric_limits<uint64_t>::max()) {
+ if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) {
+ index = lmdb::from_sv<uint64_t>(indexVal);
} else {
- if (cursor.get(indexVal, event_id, MDB_SET)) {
- index = lmdb::from_sv<uint64_t>(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<uint64_t>(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<uint64_t>(indexVal);
- messages.end_of_cache = !ret;
+ messages.timeline.events.push_back(std::move(te.data));
+ }
+ cursor.close();
+
+ // std::reverse(timeline.events.begin(), timeline.events.end());
+ messages.next_index = lmdb::from_sv<uint64_t>(indexVal);
+ messages.end_of_cache = !ret;
- return messages;
+ return messages;
}
std::optional<mtx::events::collections::TimelineEvent>
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<std::string>
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<std::string> related_ids;
+ std::vector<std::string> 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<QString, RoomInfo>
Cache::roomInfo(bool withInvites)
{
- QMap<QString, RoomInfo> result;
+ QMap<QString, RoomInfo> 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::TimelineRange>
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<uint64_t>(indexVal);
+ TimelineRange range{};
+ range.last = lmdb::from_sv<uint64_t>(indexVal);
- if (!cursor.get(indexVal, val, MDB_FIRST)) {
- return {};
- }
- range.first = lmdb::from_sv<uint64_t>(indexVal);
+ if (!cursor.get(indexVal, val, MDB_FIRST)) {
+ return {};
+ }
+ range.first = lmdb::from_sv<uint64_t>(indexVal);
- return range;
+ return range;
}
std::optional<uint64_t>
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<uint64_t>(val);
+ return lmdb::from_sv<uint64_t>(val);
}
std::optional<uint64_t>
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<uint64_t>(val);
+ return lmdb::from_sv<uint64_t>(val);
}
std::optional<std::pair<uint64_t, std::string>>
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<uint64_t>(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>();
- std::string_view temp;
- if (timelineDb.get(txn, evId, temp)) {
- return std::pair{prevIdx, std::string(prevId)};
- } else {
- prevIdx = lmdb::from_sv<uint64_t>(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<uint64_t>(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>();
+ 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<uint64_t>(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<uint64_t>
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<uint64_t>(val);
+ return lmdb::from_sv<uint64_t>(val);
}
std::optional<std::string>
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<QString, RoomInfo>
Cache::invites()
{
- QHash<QString, RoomInfo> result;
+ QHash<QString, RoomInfo> 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<RoomInfo>
Cache::invite(std::string_view roomid)
{
- std::optional<RoomInfo> result;
+ std::optional<RoomInfo> 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<Avatar> msg =
- json::parse(std::string_view(event.data(), event.size()));
+ if (res) {
+ try {
+ StateEvent<Avatar> 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<Name> msg =
- json::parse(std::string_view(event.data(), event.size()));
+ if (res) {
+ try {
+ StateEvent<Name> 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<CanonicalAlias> msg =
- json::parse(std::string_view(event.data(), event.size()));
+ if (res) {
+ try {
+ StateEvent<CanonicalAlias> 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);
+ 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<std::string, MemberInfo> members;
+ std::size_t ii = 0;
+ std::string_view user_id;
+ std::string_view member_data;
+ std::map<std::string, MemberInfo> members;
- 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());
- }
-
- 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);
+
+ auto first_member = [&members, this]() {
+ for (const auto &m : members) {
+ if (m.first != localUserId_.toStdString())
+ return QString::fromStdString(m.second.name);
+ }
- return localUserId_;
- }();
+ 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<state::JoinRules> 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<state::JoinRules> 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<GuestAccess> 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<GuestAccess> 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<Topic> msg = json::parse(event);
+ if (res) {
+ try {
+ StateEvent<Topic> 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<Create> msg = json::parse(event);
+ if (res) {
+ try {
+ StateEvent<Create> 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<Create> msg = json::parse(event);
+ if (res) {
+ try {
+ StateEvent<Create> 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<mtx::events::state::CanonicalAlias>
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<CanonicalAlias> msg = json::parse(event);
+ if (res) {
+ try {
+ StateEvent<CanonicalAlias> 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<state::Name> 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<state::Name> 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<state::Avatar> 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<state::Avatar> 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<Topic> 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<Topic> 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<Create> 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<Create> 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<std::string>
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<std::string> room_ids;
+ std::string_view id, data;
+ std::vector<std::string> 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<MemberInfo>
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<RoomMember>
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<RoomMember> 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<RoomMember> 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<RoomMember>
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);
+ auto txn = ro_txn(env_);
+ auto db = getInviteMembersDb(txn, room_id);
+ auto cursor = lmdb::cursor::open(txn, db);
- std::size_t currentIndex = 0;
+ std::size_t currentIndex = 0;
- const auto endIndex = std::min(startIndex + len, db.size(txn));
+ const auto endIndex = std::min(startIndex + len, db.size(txn));
- std::vector<RoomMember> members;
+ std::vector<RoomMember> members;
- std::string_view user_id, user_data;
- while (cursor.get(user_id, user_data, MDB_NEXT)) {
- if (currentIndex < startIndex) {
- currentIndex += 1;
- continue;
- }
-
- 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<mtx::events::collections::TimelineEvent>
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<uint64_t>::max() / 2;
- auto cursor = lmdb::cursor::open(txn, orderDb);
- if (cursor.get(indexVal, val, MDB_LAST)) {
- index = lmdb::from_sv<uint64_t>(indexVal);
- }
-
- uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2;
- auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
- if (msgCursor.get(indexVal, val, MDB_LAST)) {
- msgIndex = lmdb::from_sv<uint64_t>(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<uint64_t>::max() / 2;
+ auto cursor = lmdb::cursor::open(txn, orderDb);
+ if (cursor.get(indexVal, val, MDB_LAST)) {
+ index = lmdb::from_sv<uint64_t>(indexVal);
+ }
+
+ uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2;
+ auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
+ if (msgCursor.get(indexVal, val, MDB_LAST)) {
+ msgIndex = lmdb::from_sv<uint64_t>(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<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&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<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(
- &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<uint64_t>::max() / 2;
- {
- auto cursor = lmdb::cursor::open(txn, orderDb);
- if (cursor.get(indexVal, val, MDB_FIRST)) {
- index = lmdb::from_sv<uint64_t>(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<uint64_t>::max() / 2;
- {
- auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
- if (msgCursor.get(indexVal, val, MDB_FIRST)) {
- msgIndex = lmdb::from_sv<uint64_t>(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<uint64_t>::max() / 2;
+ {
+ auto cursor = lmdb::cursor::open(txn, orderDb);
+ if (cursor.get(indexVal, val, MDB_FIRST)) {
+ index = lmdb::from_sv<uint64_t>(indexVal);
}
+ }
- std::string event_id_val;
- for (const auto &e : res.chunk) {
- if (std::holds_alternative<
- mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
- continue;
-
- auto event = mtx::accessors::serialize_event(e);
- event_id_val = event["event_id"].get<std::string>();
- 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<uint64_t>::max() / 2;
+ {
+ auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
+ if (msgCursor.get(indexVal, val, MDB_FIRST)) {
+ msgIndex = lmdb::from_sv<uint64_t>(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<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
+ continue;
+
+ auto event = mtx::accessors::serialize_event(e);
+ event_id_val = event["event_id"].get<std::string>();
+ 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<std::string>();
-
- 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<std::string>();
+
+ 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;
- if (!found)
- break;
+ 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 (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<std::string, QList<mtx::responses::Notification>> notifsByRoom;
+ QMap<std::string, QList<mtx::responses::Notification>> 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<std::string, QList<mtx::responses::Notification>>::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<std::string, QList<mtx::responses::Notification>>::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<mtx::responses::Notification> &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<std::string>
Cache::getRoomIds(lmdb::txn &txn)
{
- auto cursor = lmdb::cursor::open(txn, roomsDb_);
+ auto cursor = lmdb::cursor::open(txn, roomsDb_);
- std::vector<std::string> rooms;
+ std::vector<std::string> 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<uint64_t>(indexVal);
- } else {
- continue;
- }
- if (cursor.get(indexVal, val, MDB_FIRST)) {
- first = lmdb::from_sv<uint64_t>(indexVal);
- } else {
- continue;
- }
+ uint64_t first, last;
+ if (cursor.get(indexVal, val, MDB_LAST)) {
+ last = lmdb::from_sv<uint64_t>(indexVal);
+ } else {
+ continue;
+ }
+ if (cursor.get(indexVal, val, MDB_FIRST)) {
+ first = lmdb::from_sv<uint64_t>(indexVal);
+ } else {
+ continue;
+ }
- size_t message_count = static_cast<size_t>(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<std::string>();
- 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<size_t>(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<std::string>();
+ 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<std::string> &spaces_with_updates,
std::set<std::string> 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<mtx::events::state::space::Child>(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<mtx::events::state::space::Child>(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<mtx::events::state::space::Parent>(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<mtx::events::state::space::Parent>(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<mtx::events::state::PowerLevels>(txn, space);
+ auto pls = getStateEvent<mtx::events::state::PowerLevels>(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<QString, std::optional<RoomInfo>>
Cache::spaces()
{
- auto txn = ro_txn(env_);
-
- QMap<QString, std::optional<RoomInfo>> 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<QString, std::optional<RoomInfo>> 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<std::string>
Cache::getParentRoomIds(const std::string &room_id)
{
- auto txn = ro_txn(env_);
+ auto txn = ro_txn(env_);
- std::vector<std::string> 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<std::string> 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<std::string>
Cache::getChildRoomIds(const std::string &room_id)
{
- auto txn = ro_txn(env_);
+ auto txn = ro_txn(env_);
- std::vector<std::string> 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<std::string> 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<ImagePackInfo>
Cache::getImagePacks(const std::string &room_id, std::optional<bool> stickers)
{
- auto txn = ro_txn(env_);
- std::vector<ImagePackInfo> 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<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(
- &*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<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
- &*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<mtx::events::msc2545::ImagePack>(
- txn, room_id2, state_id))
- addPack(pack->content, room_id2, state_id);
- }
- }
+ auto txn = ro_txn(env_);
+ std::vector<ImagePackInfo> 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<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(&*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<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
+ &*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<mtx::events::msc2545::ImagePack>(txn, room_id2, state_id))
+ addPack(pack->content, room_id2, state_id);
}
+ }
}
+ }
- // packs from current room
- if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) {
- addPack(pack->content, room_id, "");
- }
- for (const auto &pack :
- getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) {
- addPack(pack.content, room_id, pack.state_key);
- }
+ // packs from current room
+ if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) {
+ addPack(pack->content, room_id, "");
+ }
+ for (const auto &pack : getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) {
+ addPack(pack.content, room_id, pack.state_key);
+ }
- return infos;
+ return infos;
}
std::optional<mtx::events::collections::RoomAccountDataEvents>
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<mtx::events::collections::RoomAccountDataEvents>
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<mtx::events::EventType> &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<int64_t>::max();
- int64_t user_level = std::numeric_limits<int64_t>::min();
+ int64_t min_event_level = std::numeric_limits<int64_t>::max();
+ int64_t user_level = std::numeric_limits<int64_t>::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<PowerLevels> msg =
- json::parse(std::string_view(event.data(), event.size()));
+ if (res) {
+ try {
+ StateEvent<PowerLevels> 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<std::string>
Cache::roomMembers(const std::string &room_id)
{
- auto txn = ro_txn(env_);
+ auto txn = ro_txn(env_);
- std::vector<std::string> members;
- std::string_view user_id, unused;
+ std::vector<std::string> 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<std::string> 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<std::string> 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<std::string, std::optional<UserKeyCache>>
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<std::string, std::optional<UserKeyCache>> 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<UserKeyCache>();
- 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<std::string, std::optional<UserKeyCache>> 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<UserKeyCache>();
+ 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<std::string, mtx::crypto::DeviceKeys>{});
- info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{});
- info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{});
- 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<std::string, mtx::crypto::DeviceKeys>{});
+ info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{});
+ info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{});
+ 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<UserKeyCache>
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<UserKeyCache>
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<UserKeyCache>();
- } 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<UserKeyCache>();
+ } 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<std::string, UserKeyCache> 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<UserKeyCache>();
- 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<std::string, UserKeyCache> 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;
- 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;
+ std::string_view oldKeys;
+ auto res = db.get(txn, user, oldKeys);
+
+ if (res) {
+ updateToWrite = json::parse(oldKeys).get<UserKeyCache>();
+ 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<std::string, VerificationStatus> tmp;
- const auto local_user = utils::localUser().toStdString();
+ std::map<std::string, VerificationStatus> tmp;
+ const auto local_user = utils::localUser().toStdString();
- {
- std::unique_lock<std::mutex> 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<std::mutex> 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<std::string> &user_ids,
const std::string &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;
+ mtx::requests::QueryKeys query;
+ query.token = sync_token;
- UserKeyCache cacheEntry;
- auto res = db.get(txn, user, oldKeys);
- if (res) {
- cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size()))
- .get<UserKeyCache>();
- }
- 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<UserKeyCache>();
}
+ cacheEntry.last_changed = sync_token;
- 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<int>(err->status_code));
- return;
- }
+ db.put(txn, user, json(cacheEntry).dump());
- emit userKeysUpdate(sync_token, keys);
- });
+ query.device_keys[user] = {};
+ }
+
+ 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<int>(err->status_code));
+ return;
+ }
+
+ emit userKeysUpdate(sync_token, keys);
+ });
}
void
Cache::query_keys(const std::string &user_id,
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> 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<int>(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<int>(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<std::set<std::string>>();
- info.device_blocked = j.at("device_blocked").get<std::set<std::string>>();
+ info.device_verified = j.at("device_verified").get<std::set<std::string>>();
+ info.device_blocked = j.at("device_blocked").get<std::set<std::string>>();
}
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<std::string>();
- info.algorithm = j.at("a").get<std::string>();
+ info.version = j.at("v").get<std::string>();
+ info.algorithm = j.at("a").get<std::string>();
}
std::optional<VerificationCache>
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<std::string, VerificationStatus> tmp;
- {
- std::unique_lock<std::mutex> 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<std::string, VerificationStatus> tmp;
+ {
+ std::unique_lock<std::mutex> 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<std::string, VerificationStatus> tmp;
- {
- std::unique_lock<std::mutex> 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<std::string, VerificationStatus> tmp;
+ {
+ std::unique_lock<std::mutex> 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<std::mutex> lock(verification_storage.verification_storage_mtx);
- if (verification_storage.status.count(user_id))
- return verification_storage.status.at(user_id);
+ std::unique_lock<std::mutex> 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<std::string, std::string> &keys,
- const std::string &keyOwner) {
- if (!toVerif.signatures.count(keyOwner))
- return false;
+ auto verifyAtLeastOneSig = [](const auto &toVerif,
+ const std::map<std::string, std::string> &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<int>(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<int>(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<std::vector<std::string>>();
+ if (j.count("tags"))
+ info.tags = j.at("tags").get<std::vector<std::string>>();
}
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<std::string>();
- key.room_id = j.at("room_id").get<std::string>();
+ key.event_id = j.at("event_id").get<std::string>();
+ key.room_id = j.at("room_id").get<std::string>();
}
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<decltype(msg.deviceids)>();
+ msg.deviceids = obj.at("deviceids").get<decltype(msg.deviceids)>();
}
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<std::map<std::string, DeviceKeysToMsgIndex>>();
+ msg.keys = obj.at("keys").get<std::map<std::string, DeviceKeysToMsgIndex>>();
}
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<std::string>{});
+ msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", "");
+ msg.forwarding_curve25519_key_chain =
+ obj.value("forwarding_curve25519_key_chain", std::vector<std::string>{});
- msg.currently = obj.value("currently", SharedWithUsers{});
+ msg.currently = obj.value("currently", SharedWithUsers{});
- msg.indices = obj.value("indices", std::map<uint32_t, std::string>());
+ msg.indices = obj.value("indices", std::map<uint32_t, std::string>());
}
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<uint64_t>();
- msg.pickled_session = obj.at("s").get<std::string>();
+ msg.last_message_ts = obj.at("ts").get<uint64_t>();
+ msg.pickled_session = obj.at("s").get<std::string>();
}
namespace cache {
void
init(const QString &user_id)
{
- qRegisterMetaType<RoomMember>();
- qRegisterMetaType<RoomSearchResult>();
- qRegisterMetaType<RoomInfo>();
- qRegisterMetaType<QMap<QString, RoomInfo>>();
- qRegisterMetaType<std::map<QString, RoomInfo>>();
- qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>();
- qRegisterMetaType<mtx::responses::QueryKeys>();
+ qRegisterMetaType<RoomMember>();
+ qRegisterMetaType<RoomSearchResult>();
+ qRegisterMetaType<RoomInfo>();
+ qRegisterMetaType<QMap<QString, RoomInfo>>();
+ qRegisterMetaType<std::map<QString, RoomInfo>>();
+ qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>();
+ qRegisterMetaType<mtx::responses::QueryKeys>();
- instance_ = std::make_unique<Cache>(user_id);
+ instance_ = std::make_unique<Cache>(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<UserKeyCache>
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>
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<std::string>
joinedRooms()
{
- return instance_->joinedRooms();
+ return instance_->joinedRooms();
}
QMap<QString, RoomInfo>
roomInfo(bool withInvites)
{
- return instance_->roomInfo(withInvites);
+ return instance_->roomInfo(withInvites);
}
QHash<QString, RoomInfo>
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<RoomMember>
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<RoomMember>
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<QString>
roomIds()
{
- return instance_->roomIds();
+ return instance_->roomIds();
}
QMap<QString, mtx::responses::Notifications>
getTimelineMentions()
{
- return instance_->getTimelineMentions();
+ return instance_->getTimelineMentions();
}
//! Retrieve all the user ids from a room.
std::vector<std::string>
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<mtx::events::EventType> &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<uint64_t>
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<std::pair<uint64_t, std::string>>
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<std::string>
roomsWithStateUpdates(const mtx::responses::Sync &res)
{
- return instance_->roomsWithStateUpdates(res);
+ return instance_->roomsWithStateUpdates(res);
}
std::map<QString, RoomInfo>
getRoomInfo(const std::vector<std::string> &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<std::string> &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<std::string>
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<GroupSessionData>
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<std::string>
getOlmSessions(const std::string &curve25519)
{
- return instance_->getOlmSessions(curve25519);
+ return instance_->getOlmSessions(curve25519);
}
std::optional<mtx::crypto::OlmSessionPtr>
getOlmSession(const std::string &curve25519, const std::string &session_id)
{
- return instance_->getOlmSession(curve25519, session_id);
+ return instance_->getOlmSession(curve25519, session_id);
}
std::optional<mtx::crypto::OlmSessionPtr>
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<std::string>
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<std::string, uint64_t> 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<std::string, uint64_t> deviceids;
};
struct SharedWithUsers
{
- // userid to keys
- std::map<std::string, DeviceKeysToMsgIndex> keys;
+ // userid to keys
+ std::map<std::string, DeviceKeysToMsgIndex> 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<std::string> forwarding_curve25519_key_chain;
+ std::string sender_claimed_ed25519_key;
+ std::vector<std::string> forwarding_curve25519_key_chain;
- //! map from index to event_id to check for replay attacks
- std::map<uint32_t, std::string> indices;
+ //! map from index to event_id to check for replay attacks
+ std::map<uint32_t, std::string> 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<std::string> verified_devices;
- //! Map from sender key/curve25519 to trust status
- std::map<std::string, crypto::Trust> 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<std::string> verified_devices;
+ //! Map from sender key/curve25519 to trust status
+ std::map<std::string, crypto::Trust> 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<std::string, VerificationStatus> status;
- std::mutex verification_storage_mtx;
+ //! mapping of user to verification status
+ std::map<std::string, VerificationStatus> 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<std::string, mtx::crypto::DeviceKeys> 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<std::string> seen_device_keys;
- //! Device ids that were already used at least once
- std::set<std::string> seen_device_ids;
+ //! Device id to device keys
+ std::map<std::string, mtx::crypto::DeviceKeys> 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<std::string> seen_device_keys;
+ //! Device ids that were already used at least once
+ std::set<std::string> 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<std::string> device_verified;
- //! list of devices the user blocks
- std::set<std::string> device_blocked;
+ //! list of verified device_ids with device-verification
+ std::set<std::string> device_verified;
+ //! list of devices the user blocks
+ std::set<std::string> 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<std::string> 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<std::string> 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<std::string, std::optional<UserKeyCache>> 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<std::string> &user_ids,
- const std::string &sync_token);
- void query_keys(const std::string &user_id,
- std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
-
- // device & user verification cache
- std::optional<UserKeyCache> 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<std::string> joinedRooms();
-
- QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
- std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
- QHash<QString, RoomInfo> invites();
- std::optional<RoomInfo> invite(std::string_view roomid);
- QMap<QString, std::optional<RoomInfo>> 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<typename T>
- std::optional<mtx::events::StateEvent<T>> getStateEvent(const std::string &room_id,
- std::string_view state_key = "")
- {
- auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
- return getStateEvent<T>(txn, room_id, state_key);
- }
-
- //! retrieve a specific event from account data
- //! pass empty room_id for global account data
- std::optional<mtx::events::collections::RoomAccountDataEvents> getAccountData(
- mtx::events::EventType type,
- const std::string &room_id = "");
-
- //! Retrieve member info from a room.
- std::vector<RoomMember> getMembers(const std::string &room_id,
- std::size_t startIndex = 0,
- std::size_t len = 30);
-
- std::vector<RoomMember> 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<QString> roomIds();
- QMap<QString, mtx::responses::Notifications> getTimelineMentions();
-
- //! Retrieve all the user ids from a room.
- std::vector<std::string> 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<mtx::events::EventType> &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<std::string, std::optional<UserKeyCache>> 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<std::string> &user_ids,
+ const std::string &sync_token);
+ void query_keys(const std::string &user_id,
+ std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
+
+ // device & user verification cache
+ std::optional<UserKeyCache> 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<std::string> joinedRooms();
+
+ QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
+ std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
+ QHash<QString, RoomInfo> invites();
+ std::optional<RoomInfo> invite(std::string_view roomid);
+ QMap<QString, std::optional<RoomInfo>> 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<typename T>
+ std::optional<mtx::events::StateEvent<T>> getStateEvent(const std::string &room_id,
+ std::string_view state_key = "")
+ {
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ return getStateEvent<T>(txn, room_id, state_key);
+ }
+
+ //! retrieve a specific event from account data
+ //! pass empty room_id for global account data
+ std::optional<mtx::events::collections::RoomAccountDataEvents> getAccountData(
+ mtx::events::EventType type,
+ const std::string &room_id = "");
+
+ //! Retrieve member info from a room.
+ std::vector<RoomMember> getMembers(const std::string &room_id,
+ std::size_t startIndex = 0,
+ std::size_t len = 30);
+
+ std::vector<RoomMember> 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<QString> roomIds();
+ QMap<QString, mtx::responses::Notifications> getTimelineMentions();
+
+ //! Retrieve all the user ids from a room.
+ std::vector<std::string> 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<mtx::events::EventType> &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<std::string, std::map<std::string, uint64_t>>;
+ 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<uint64_t, std::string, std::greater<uint64_t>>;
+ UserReceipts readReceipts(const QString &event_id, const QString &room_id);
+
+ RoomInfo singleRoomInfo(const std::string &room_id);
+ std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
+ std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &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<std::string, std::map<std::string, uint64_t>>;
- 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<uint64_t, std::string, std::greater<uint64_t>>;
- UserReceipts readReceipts(const QString &event_id, const QString &room_id);
-
- RoomInfo singleRoomInfo(const std::string &room_id);
- std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
- std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &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<uint64_t>::max(),
- bool forward = false);
-
- std::optional<mtx::events::collections::TimelineEvent> 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<std::string> relatedEvents(const std::string &room_id,
- const std::string &event_id);
-
- struct TimelineRange
- {
- uint64_t first, last;
+ uint64_t index = std::numeric_limits<uint64_t>::max(),
+ bool forward = false);
+
+ std::optional<mtx::events::collections::TimelineEvent> 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<std::string> relatedEvents(const std::string &room_id, const std::string &event_id);
+
+ struct TimelineRange
+ {
+ uint64_t first, last;
+ };
+ std::optional<TimelineRange> getTimelineRange(const std::string &room_id);
+ std::optional<uint64_t> getTimelineIndex(const std::string &room_id, std::string_view event_id);
+ std::optional<uint64_t> getEventIndex(const std::string &room_id, std::string_view event_id);
+ std::optional<std::pair<uint64_t, std::string>> lastInvisibleEventAfter(
+ const std::string &room_id,
+ std::string_view event_id);
+ std::optional<std::string> getTimelineEventId(const std::string &room_id, uint64_t index);
+ std::optional<uint64_t> 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<mtx::events::collections::TimelineEvent> 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<std::string> getRoomIds(lmdb::txn &txn);
+ std::vector<std::string> getParentRoomIds(const std::string &room_id);
+ std::vector<std::string> getChildRoomIds(const std::string &room_id);
+
+ std::vector<ImagePackInfo> getImagePacks(const std::string &room_id,
+ std::optional<bool> 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<mtx::events::state::Encryption> 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<GroupSessionData> getMegolmSessionData(const MegolmSessionIndex &index);
+
+ //
+ // Olm Sessions
+ //
+ void saveOlmSession(const std::string &curve25519,
+ mtx::crypto::OlmSessionPtr session,
+ uint64_t timestamp);
+ std::vector<std::string> getOlmSessions(const std::string &curve25519);
+ std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
+ const std::string &session_id);
+ std::optional<mtx::crypto::OlmSessionPtr> getLatestOlmSession(const std::string &curve25519);
+
+ void saveOlmAccount(const std::string &pickled);
+ std::string restoreOlmAccount();
+
+ void saveBackupVersion(const OnlineBackupVersion &data);
+ void deleteBackupVersion();
+ std::optional<OnlineBackupVersion> 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<std::string> secret(const std::string name, bool internal = false);
+
+ std::string pickleSecret();
+
+ template<class T>
+ constexpr static bool isStateEvent_ =
+ std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
+ mtx::events::StateEvent<decltype(std::declval<T>().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<const char *>(v->mv_data), v->mv_size))
+ .value("key", "");
};
- std::optional<TimelineRange> getTimelineRange(const std::string &room_id);
- std::optional<uint64_t> getTimelineIndex(const std::string &room_id,
- std::string_view event_id);
- std::optional<uint64_t> getEventIndex(const std::string &room_id,
- std::string_view event_id);
- std::optional<std::pair<uint64_t, std::string>> lastInvisibleEventAfter(
- const std::string &room_id,
- std::string_view event_id);
- std::optional<std::string> getTimelineEventId(const std::string &room_id, uint64_t index);
- std::optional<uint64_t> 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<mtx::events::collections::TimelineEvent> 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<std::string> getRoomIds(lmdb::txn &txn);
- std::vector<std::string> getParentRoomIds(const std::string &room_id);
- std::vector<std::string> getChildRoomIds(const std::string &room_id);
-
- std::vector<ImagePackInfo> getImagePacks(const std::string &room_id,
- std::optional<bool> 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<mtx::events::state::Encryption> 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<GroupSessionData> getMegolmSessionData(const MegolmSessionIndex &index);
-
- //
- // Olm Sessions
- //
- void saveOlmSession(const std::string &curve25519,
- mtx::crypto::OlmSessionPtr session,
- uint64_t timestamp);
- std::vector<std::string> getOlmSessions(const std::string &curve25519);
- std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
- const std::string &session_id);
- std::optional<mtx::crypto::OlmSessionPtr> getLatestOlmSession(
- const std::string &curve25519);
-
- void saveOlmAccount(const std::string &pickled);
- std::string restoreOlmAccount();
-
- void saveBackupVersion(const OnlineBackupVersion &data);
- void deleteBackupVersion();
- std::optional<OnlineBackupVersion> 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<std::string> secret(const std::string name, bool internal = false);
-
- std::string pickleSecret();
-
- template<class T>
- constexpr static bool isStateEvent_ =
- std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
- mtx::events::StateEvent<decltype(std::declval<T>().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<const char *>(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<QString> &event_ids);
- void roomReadStatus(const std::map<QString, bool> &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<QString> &event_ids);
+ void roomReadStatus(const std::map<QString, bool> &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<mtx::responses::Notification> &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<MemberInfo> 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<mtx::events::collections::RoomAccountDataEvents>
+ 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<class T>
+ 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<T> &events)
+ {
+ for (const auto &e : events)
+ saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e);
+ }
+
+ template<class T>
+ 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<mtx::responses::Notification> &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<MemberInfo> 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<mtx::events::collections::RoomAccountDataEvents>
- 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<class T>
- 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<T> &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<StateEvent<Member>>(&event); e != nullptr) {
+ switch (e->content.membership) {
+ //
+ // We only keep users with invite or join membership.
+ //
+ case Membership::Invite:
+ case Membership::Join: {
+ auto display_name =
+ e->content.display_name.empty() ? e->state_key : e->content.display_name;
+
+ // 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<StateEvent<Encryption>>(event)) {
+ setEncryptedRoom(txn, room_id);
+ return;
}
- template<class T>
- 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<StateEvent<Member>>(&event); e != nullptr) {
- switch (e->content.membership) {
- //
- // We only keep users with invite or join membership.
- //
- case Membership::Invite:
- case Membership::Join: {
- auto display_name = e->content.display_name.empty()
- ? e->state_key
- : e->content.display_name;
-
- // 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<StateEvent<Encryption>>(event)) {
- setEncryptedRoom(txn, room_id);
- return;
- }
-
- std::visit(
- [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) {
- if constexpr (isStateEvent_<decltype(e)>) {
- 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<decltype(e)>>,
- StateEvent<mtx::events::msg::Redacted>>) {
- 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_<decltype(e)>) {
+ 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<decltype(e)>>,
+ StateEvent<mtx::events::msg::Redacted>>) {
+ 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<typename T>
+ std::optional<mtx::events::StateEvent<T>> getStateEvent(lmdb::txn &txn,
+ const std::string &room_id,
+ std::string_view state_key = "")
+ {
+ constexpr auto type = mtx::events::state_content_to_type<T>;
+ 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<std::string>(), value))
+ return std::nullopt;
+ } catch (std::exception &e) {
+ return std::nullopt;
+ }
}
- template<typename T>
- std::optional<mtx::events::StateEvent<T>> getStateEvent(lmdb::txn &txn,
- const std::string &room_id,
- std::string_view state_key = "")
- {
- constexpr auto type = mtx::events::state_content_to_type<T>;
- 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<std::string>(), value))
- return std::nullopt;
- } catch (std::exception &e) {
- return std::nullopt;
- }
- }
-
- try {
- return json::parse(value).get<mtx::events::StateEvent<T>>();
- } catch (std::exception &e) {
- return std::nullopt;
- }
+ try {
+ return json::parse(value).get<mtx::events::StateEvent<T>>();
+ } catch (std::exception &e) {
+ return std::nullopt;
}
+ }
- template<typename T>
- std::vector<mtx::events::StateEvent<T>> getStateEventsWithType(lmdb::txn &txn,
- const std::string &room_id)
+ template<typename T>
+ std::vector<mtx::events::StateEvent<T>> getStateEventsWithType(lmdb::txn &txn,
+ const std::string &room_id)
- {
- constexpr auto type = mtx::events::state_content_to_type<T>;
- static_assert(type != mtx::events::EventType::Unsupported,
- "Not a supported type in state events.");
-
- if (room_id.empty())
- return {};
-
- std::vector<mtx::events::StateEvent<T>> 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<std::string>(),
- value))
- events.push_back(
- json::parse(value)
- .get<mtx::events::StateEvent<T>>());
- }
- }
- }
+ {
+ constexpr auto type = mtx::events::state_content_to_type<T>;
+ static_assert(type != mtx::events::EventType::Unsupported,
+ "Not a supported type in state events.");
- return events;
- }
- void saveInvites(lmdb::txn &txn,
- const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
+ if (room_id.empty())
+ return {};
- void savePresence(
- lmdb::txn &txn,
- const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates);
+ std::vector<mtx::events::StateEvent<T>> events;
- //! Sends signals for the rooms that are removed.
- void removeLeftRooms(lmdb::txn &txn,
- const std::map<std::string, mtx::responses::LeftRoom> &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<std::string>(), value))
+ events.push_back(json::parse(value).get<mtx::events::StateEvent<T>>());
}
+ }
}
- void updateSpaces(lmdb::txn &txn,
- const std::set<std::string> &spaces_with_updates,
- std::set<std::string> 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<std::string, mtx::responses::InvitedRoom> &rooms);
- lmdb::dbi getPresenceDb(lmdb::txn &txn)
- {
- return lmdb::dbi::open(txn, "presence", MDB_CREATE);
- }
+ void savePresence(
+ lmdb::txn &txn,
+ const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &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<std::string, mtx::responses::LeftRoom> &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<mtx::events::state::Member> &event)
- {
- if (!event.content.display_name.empty())
- return QString::fromStdString(event.content.display_name);
-
- return QString::fromStdString(event.state_key);
- }
-
- std::optional<VerificationCache> verificationCache(const std::string &user_id,
- lmdb::txn &txn);
- VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
- std::optional<UserKeyCache> 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<std::string> &spaces_with_updates,
+ std::set<std::string> 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<mtx::events::state::Member> &event)
+ {
+ if (!event.content.display_name.empty())
+ return QString::fromStdString(event.content.display_name);
+
+ return QString::fromStdString(event.state_key);
+ }
+
+ std::optional<VerificationCache> verificationCache(const std::string &user_id, lmdb::txn &txn);
+ VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
+ std::optional<UserKeyCache> 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<std::string> frameRates;
- };
- std::string name;
- GstDevice *device;
- std::vector<Caps> caps;
+ struct Caps
+ {
+ std::string resolution;
+ std::vector<std::string> frameRates;
+ };
+ std::string name;
+ GstDevice *device;
+ std::vector<Caps> caps;
};
std::vector<AudioSource> audioSources_;
@@ -50,315 +50,304 @@ using FrameRate = std::pair<int, int>;
std::optional<FrameRate>
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<std::string> &rates, const FrameRate &rate)
{
- constexpr double minimumFrameRate = 15.0;
- if (static_cast<double>(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<double>(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<typename T>
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<typename T>
std::vector<std::string>
deviceNames(T &sources, const std::string &defaultDevice)
{
- std::vector<std::string> ret;
- ret.reserve(sources.size());
- for (const auto &s : sources)
- ret.push_back(s.name);
+ std::vector<std::string> 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<VideoSource>
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<int, int>
tokenise(std::string_view str, char delim)
{
- std::pair<int, int> 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<int, int> 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<std::string>
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<std::string>
CallDevices::resolutions(const std::string &cameraName) const
{
- std::vector<std::string> 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<std::string> 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<std::string>
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<int, int> &resolution, std::pair<int, int> &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<int, int> &resolution, std::pair<int, int> &f
bool
CallDevices::haveMic() const
{
- return false;
+ return false;
}
bool
CallDevices::haveCamera() const
{
- return false;
+ return false;
}
std::vector<std::string>
CallDevices::names(bool, const std::string &) const
{
- return {};
+ return {};
}
std::vector<std::string>
CallDevices::resolutions(const std::string &) const
{
- return {};
+ return {};
}
std::vector<std::string>
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<std::string> names(bool isVideo, const std::string &defaultDevice) const;
- std::vector<std::string> resolutions(const std::string &cameraName) const;
- std::vector<std::string> 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<std::string> names(bool isVideo, const std::string &defaultDevice) const;
+ std::vector<std::string> resolutions(const std::string &cameraName) const;
+ std::vector<std::string> 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<int, int> &resolution,
- std::pair<int, int> &frameRate) const;
+ friend class WebRTCSession;
+ void init();
+ GstDevice *audioDevice() const;
+ GstDevice *videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &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<std::vector<mtx::events::msg::CallCandidates::Candidate>>();
- qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
- qRegisterMetaType<mtx::responses::TurnServer>();
-
- connect(
- &session_,
- &WebRTCSession::offerCreated,
- this,
- [this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &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<std::vector<mtx::events::msg::CallCandidates::Candidate>>();
+ qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
+ qRegisterMetaType<mtx::responses::TurnServer>();
+
+ connect(
+ &session_,
+ &WebRTCSession::offerCreated,
+ this,
+ [this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &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<CallCandidates::Candidate> &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<CallCandidates::Candidate> &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<QMediaPlayer::Error>::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<QMediaPlayer::Error>::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<RoomMember> 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<RoomMember> 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<CallInvite>(event) || handleEvent<CallCandidates>(event) ||
- handleEvent<CallAnswer>(event) || handleEvent<CallHangUp>(event))
- return;
+ if (handleEvent<CallInvite>(event) || handleEvent<CallCandidates>(event) ||
+ handleEvent<CallAnswer>(event) || handleEvent<CallHangUp>(event))
+ return;
#else
- (void)event;
+ (void)event;
#endif
}
@@ -261,325 +252,321 @@ template<typename T>
bool
CallManager::handleEvent(const mtx::events::collections::TimelineEvents &event)
{
- if (std::holds_alternative<RoomEvent<T>>(event)) {
- handleEvent(std::get<RoomEvent<T>>(event));
- return true;
- }
- return false;
+ if (std::holds_alternative<RoomEvent<T>>(event)) {
+ handleEvent(std::get<RoomEvent<T>>(event));
+ return true;
+ }
+ return false;
}
void
CallManager::handleEvent(const RoomEvent<CallInvite> &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<RoomMember> 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<RoomMember> 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<CallCandidates> &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<CallAnswer> &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<CallHangUp> &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<std::string> 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<std::string> 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<milliseconds>(system_clock::now().time_since_epoch()).count();
- callid_ = "c" + std::to_string(ms);
+ using namespace std::chrono;
+ uint64_t ms = duration_cast<milliseconds>(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<xcb_connection_t, std::function<void(xcb_connection_t *)>> 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<xcb_connection_t, std::function<void(xcb_connection_t *)>> 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<xcb_ewmh_connection_t, std::function<void(xcb_ewmh_connection_t *)>>
+ 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<xcb_ewmh_connection_t, std::function<void(xcb_ewmh_connection_t *)>>
- 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<std::string>
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<std::string> 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<std::string> 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<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
- std::vector<std::string> turnURIs_;
- QTimer turnServerTimer_;
- QMediaPlayer player_;
- std::vector<std::pair<QString, uint32_t>> 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<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
+ std::vector<std::string> turnURIs_;
+ QTimer turnServerTimer_;
+ QMediaPlayer player_;
+ std::vector<std::pair<QString, uint32_t>> windows_;
- template<typename T>
- bool handleEvent(const mtx::events::collections::TimelineEvents &event);
- void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
- void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
- void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
- void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
- 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<typename T>
+ bool handleEvent(const mtx::events::collections::TimelineEvents &event);
+ void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
+ void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
+ void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
+ void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
+ 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> userSettings, QWidget *parent)
, notificationsManager(this)
, callManager_(new CallManager(this))
{
- setObjectName("chatPage");
-
- instance_ = this;
-
- qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
- qRegisterMetaType<std::optional<RelatedInfo>>();
- qRegisterMetaType<mtx::presence::PresenceState>();
- qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
- qRegisterMetaType<SecretsToDecrypt>();
-
- 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<std::optional<mtx::crypto::EncryptedFile>>();
+ qRegisterMetaType<std::optional<RelatedInfo>>();
+ qRegisterMetaType<mtx::presence::PresenceState>();
+ qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
+ qRegisterMetaType<SecretsToDecrypt>();
- 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<int>(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<int>(err->status_code));
+ return;
+ }
- connectCallMessage<mtx::events::msg::CallInvite>();
- connectCallMessage<mtx::events::msg::CallCandidates>();
- connectCallMessage<mtx::events::msg::CallAnswer>();
- connectCallMessage<mtx::events::msg::CallHangUp>();
+ 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<mtx::events::msg::CallInvite>();
+ connectCallMessage<mtx::events::msg::CallCandidates>();
+ connectCallMessage<mtx::events::msg::CallAnswer>();
+ connectCallMessage<mtx::events::msg::CallHangUp>();
}
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<User>(userid.toStdString()));
- } catch (const std::invalid_argument &) {
- nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
- userid.toStdString());
- }
+ try {
+ http::client()->set_user(parse<User>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<std::string> &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<int>(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<int>(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<int>(err->status_code),
- static_cast<int>(err->error_code));
-
- if (err->status_code < 400 || err->status_code >= 500)
- return;
- }
-
- std::map<std::string, uint16_t> 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<int>(err->status_code),
+ static_cast<int>(err->error_code));
+
+ if (err->status_code < 400 || err->status_code >= 500)
+ return;
+ }
+
+ std::map<std::string, uint16_t> 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<std::string, uint16_t> &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<int>(err->status_code),
static_cast<int>(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<int>(err->status_code),
static_cast<int>(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<std::string>() != 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<std::string>() != 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<typename T>
void
ChatPage::connectCallMessage()
{
- connect(callManager_,
- qOverload<const QString &, const T &>(&CallManager::newMessage),
- view_manager_,
- qOverload<const QString &, const T &>(&TimelineViewManager::queueCallMessage));
+ connect(callManager_,
+ qOverload<const QString &, const T &>(&CallManager::newMessage),
+ view_manager_,
+ qOverload<const QString &, const T &>(&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<std::string> 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<std::string> 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<std::string, mtx::secret_storage::AesHmacSha2E
class ChatPage : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
+ ChatPage(QSharedPointer<UserSettings> 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> userSettings() { return userSettings_; }
- CallManager *callManager() { return callManager_; }
- TimelineViewManager *timelineManager() { return view_manager_; }
- void deleteConfigs();
+ QSharedPointer<UserSettings> 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<std::string> &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<std::string> &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<QString, mtx::responses::Notifications> ¬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<mtx::events::msg::KeyVerificationRequest> &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<QString, mtx::responses::Notifications> ¬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<mtx::events::msg::KeyVerificationRequest> &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<std::string, uint16_t> &counts);
- void getProfileInfo();
- void getBackupVersion();
+ void startInitialSync();
+ void tryInitialSync();
+ void trySync();
+ void verifyOneTimeKeyCountAfterStartup();
+ void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &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<mtx::events::state::Member>;
- using Memberships = std::map<std::string, Membership>;
+ using UserID = QString;
+ using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
+ using Memberships = std::map<std::string, Membership>;
- void loadStateFromCache();
- void resetUI();
+ void loadStateFromCache();
+ void resetUI();
- template<class Collection>
- Memberships getMemberships(const std::vector<Collection> &events) const;
+ template<class Collection>
+ Memberships getMemberships(const std::vector<Collection> &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<typename T>
- void connectCallMessage();
+ template<typename T>
+ 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> userSettings_;
+ // Global user settings.
+ QSharedPointer<UserSettings> userSettings_;
- NotificationsManager notificationsManager;
- CallManager *callManager_;
+ NotificationsManager notificationsManager;
+ CallManager *callManager_;
};
template<class Collection>
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>>
ChatPage::getMemberships(const std::vector<Collection> &collection) const
{
- std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
+ std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
- using Member = mtx::events::StateEvent<mtx::events::state::Member>;
+ using Member = mtx::events::StateEvent<mtx::events::state::Member>;
- for (const auto &event : collection) {
- if (auto member = std::get_if<Member>(event)) {
- memberships.emplace(member->state_key, *member);
- }
+ for (const auto &event : collection) {
+ if (auto member = std::get_if<Member>(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<int, QByteArray>
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<int, QByteArray> 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<size_t>(row) >= images.size())
- return {};
- return images.at(static_cast<size_t>(row)).image;
- }
+ enum Roles
+ {
+ Url = Qt::UserRole,
+ ShortCode,
+ Body,
+ PackName,
+ OriginalRow,
+ };
+
+ CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
+ QHash<int, QByteArray> 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<size_t>(row) >= images.size())
+ return {};
+ return images.at(static_cast<size_t>(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<ImageDesc> images;
+ std::vector<ImageDesc> 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<size_t>(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<size_t>(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<int, QByteArray>
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<int>(std::min<size_t>(max_completions_,
- std::numeric_limits<int>::max())),
- sourceModel()->rowCount());
- else
- return (int)mapping.size();
+ if (searchString_.isEmpty())
+ return std::min(
+ static_cast<int>(std::min<size_t>(max_completions_, std::numeric_limits<int>::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<typename Key, typename Value>
struct trie
{
- std::vector<Value> values;
- std::map<Key, trie> next;
-
- void insert(const QVector<Key> &keys, const Value &v)
- {
- auto t = this;
- for (const auto k : keys) {
- t = &t->next[k];
- }
-
- t->values.push_back(v);
+ std::vector<Value> values;
+ std::map<Key, trie> next;
+
+ void insert(const QVector<Key> &keys, const Value &v)
+ {
+ auto t = this;
+ for (const auto k : keys) {
+ t = &t->next[k];
}
- std::vector<Value> valuesAndSubvalues(size_t limit = -1) const
- {
- std::vector<Value> 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<Value> valuesAndSubvalues(size_t limit = -1) const
+ {
+ std::vector<Value> 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<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
- size_t result_count_limit,
- size_t max_edit_distance_ = 2) const
- {
- std::vector<Value> 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<Value> &&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<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
+ size_t result_count_limit,
+ size_t max_edit_distance_ = 2) const
+ {
+ std::vector<Value> ret;
+ if (!result_count_limit)
+ return ret;
+
+ if (keys.isEmpty())
+ return valuesAndSubvalues(result_count_limit);
+
+ auto append = [&ret, result_count_limit](std::vector<Value> &&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<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- int columnCount(const QModelIndex &) const override;
+ QHash<int, QByteArray> 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<uint, int> trie_;
- std::vector<int> mapping;
- int maxMistakes_;
- size_t max_completions_;
+ QString searchString_;
+ trie<uint, int> trie_;
+ std::vector<int> 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<mtx::identifiers::User>(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<int>(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<int>(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<mtx::identifiers::User>(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<int>(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<int>(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<std::string, std::string> 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<std::string, std::string> 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<int>(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<int>(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<int>
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<mtx::events::msg::KeyVerificationStart> 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<mtx::events::msg::KeyVerificationStart> 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<std::string, std::string> 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<std::string, std::string> key_list;
- key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
+ std::map<std::string, std::string> 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<DeviceVerificationFlow>
@@ -810,22 +777,22 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_,
QString other_user_,
QString event_id_)
{
- QSharedPointer<DeviceVerificationFlow> 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> 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>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
@@ -833,17 +800,17 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
QString other_user_,
QString txn_id_)
{
- QSharedPointer<DeviceVerificationFlow> 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<DeviceVerificationFlow> 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>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
@@ -851,32 +818,32 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
QString other_user_,
QString txn_id_)
{
- QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
- parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
- flow->transaction_id = txn_id_.toStdString();
+ QSharedPointer<DeviceVerificationFlow> 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>
DeviceVerificationFlow::InitiateUserVerification(QObject *parent_,
TimelineModel *timelineModel_,
QString userid)
{
- QSharedPointer<DeviceVerificationFlow> flow(
- new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
- flow->sender = true;
- return flow;
+ QSharedPointer<DeviceVerificationFlow> flow(
+ new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
+ flow->sender = true;
+ return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device)
{
- QSharedPointer<DeviceVerificationFlow> flow(
- new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device));
+ QSharedPointer<DeviceVerificationFlow> 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<mtx::crypto::SAS>;
// 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<int> 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<int> 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<DeviceVerificationFlow> NewInRoomVerification(
- QObject *parent_,
- TimelineModel *timelineModel_,
- const mtx::events::msg::KeyVerificationRequest &msg,
- QString other_user_,
- QString event_id_);
- static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
- QObject *parent_,
- const mtx::events::msg::KeyVerificationRequest &msg,
- QString other_user_,
- QString txn_id_);
- static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
- QObject *parent_,
- const mtx::events::msg::KeyVerificationStart &msg,
- QString other_user_,
- QString txn_id_);
- static QSharedPointer<DeviceVerificationFlow>
- InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
- static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
- QString userid,
- QString device);
-
- // getters
- QString state();
- Error error() { return error_; }
- QString getUserId();
- QString getDeviceId();
- bool getSender();
- std::vector<int> 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<DeviceVerificationFlow> NewInRoomVerification(
+ QObject *parent_,
+ TimelineModel *timelineModel_,
+ const mtx::events::msg::KeyVerificationRequest &msg,
+ QString other_user_,
+ QString event_id_);
+ static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
+ QObject *parent_,
+ const mtx::events::msg::KeyVerificationRequest &msg,
+ QString other_user_,
+ QString txn_id_);
+ static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
+ QObject *parent_,
+ const mtx::events::msg::KeyVerificationStart &msg,
+ QString other_user_,
+ QString txn_id_);
+ static QSharedPointer<DeviceVerificationFlow>
+ InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
+ static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
+ QString userid,
+ QString device);
+
+ // getters
+ QString state();
+ Error error() { return error_; }
+ QString getUserId();
+ QString getDeviceId();
+ bool getSender();
+ std::vector<int> 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<int> sasList;
- UserKeyCache their_keys;
- TimelineModel *model_;
- mtx::common::Relation relation;
-
- State state_ = PromptStartVerification;
- Error error_ = UnknownMethod;
-
- bool isMacVerified = false;
-
- template<typename T>
- void send(T msg)
- {
- if (this->type == DeviceVerificationFlow::Type::ToDevice) {
- mtx::requests::ToDeviceMessages<T> body;
- msg.transaction_id = this->transaction_id;
- body[this->toClient][deviceId.toStdString()] = msg;
-
- http::client()->send_to_device<T>(
- 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<int>(err->status_code));
- });
- } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
- if constexpr (!std::is_same_v<T,
- mtx::events::msg::KeyVerificationRequest>) {
- 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<T>);
- }
-
- nhlog::net()->debug(
- "Sent verification step: {} in state: {}",
- mtx::events::to_string(mtx::events::to_device_content_to_type<T>),
- 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<int> sasList;
+ UserKeyCache their_keys;
+ TimelineModel *model_;
+ mtx::common::Relation relation;
+
+ State state_ = PromptStartVerification;
+ Error error_ = UnknownMethod;
+
+ bool isMacVerified = false;
+
+ template<typename T>
+ void send(T msg)
+ {
+ if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+ mtx::requests::ToDeviceMessages<T> body;
+ msg.transaction_id = this->transaction_id;
+ body[this->toClient][deviceId.toStdString()] = msg;
+
+ http::client()->send_to_device<T>(
+ 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<int>(err->status_code));
+ });
+ } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
+ if constexpr (!std::is_same_v<T, mtx::events::msg::KeyVerificationRequest>) {
+ 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<T>);
}
+
+ nhlog::net()->debug("Sent verification step: {} in state: {}",
+ mtx::events::to_string(mtx::events::to_device_content_to_type<T>),
+ 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<nheko::nonesuch, void, Op,
struct IsStateEvent
{
- template<class T>
- bool operator()(const mtx::events::StateEvent<T> &)
- {
- return true;
- }
- template<class T>
- bool operator()(const mtx::events::Event<T> &)
- {
- return false;
- }
+ template<class T>
+ bool operator()(const mtx::events::StateEvent<T> &)
+ {
+ return true;
+ }
+ template<class T>
+ bool operator()(const mtx::events::Event<T> &)
+ {
+ return false;
+ }
};
struct EventMsgType
{
- template<class E>
- using msgtype_t = decltype(E::msgtype);
- template<class T>
- mtx::events::MessageType operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<msgtype_t, T>::value) {
- if constexpr (std::is_same_v<std::optional<std::string>,
- std::remove_cv_t<decltype(e.content.msgtype)>>)
- return mtx::events::getMessageType(e.content.msgtype.value());
- else if constexpr (std::is_same_v<
- std::string,
- std::remove_cv_t<decltype(e.content.msgtype)>>)
- return mtx::events::getMessageType(e.content.msgtype);
- }
- return mtx::events::MessageType::Unknown;
+ template<class E>
+ using msgtype_t = decltype(E::msgtype);
+ template<class T>
+ mtx::events::MessageType operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<msgtype_t, T>::value) {
+ if constexpr (std::is_same_v<std::optional<std::string>,
+ std::remove_cv_t<decltype(e.content.msgtype)>>)
+ return mtx::events::getMessageType(e.content.msgtype.value());
+ else if constexpr (std::is_same_v<std::string,
+ std::remove_cv_t<decltype(e.content.msgtype)>>)
+ return mtx::events::getMessageType(e.content.msgtype);
}
+ return mtx::events::MessageType::Unknown;
+ }
};
struct EventRoomName
{
- template<class T>
- std::string operator()(const T &e)
- {
- if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Name>, T>)
- return e.content.name;
- return "";
- }
+ template<class T>
+ std::string operator()(const T &e)
+ {
+ if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Name>, T>)
+ return e.content.name;
+ return "";
+ }
};
struct EventRoomTopic
{
- template<class T>
- std::string operator()(const T &e)
- {
- if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Topic>, T>)
- return e.content.topic;
- return "";
- }
+ template<class T>
+ std::string operator()(const T &e)
+ {
+ if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Topic>, T>)
+ return e.content.topic;
+ return "";
+ }
};
struct CallType
{
- template<class T>
- std::string operator()(const T &e)
- {
- if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>,
- 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<class T>
+ std::string operator()(const T &e)
+ {
+ if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>, 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<class C>
- using body_t = decltype(C::body);
- template<class T>
- std::string operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<body_t, T>::value) {
- if constexpr (std::is_same_v<std::optional<std::string>,
- std::remove_cv_t<decltype(e.content.body)>>)
- return e.content.body ? e.content.body.value() : "";
- else if constexpr (std::is_same_v<
- std::string,
- std::remove_cv_t<decltype(e.content.body)>>)
- return e.content.body;
- }
- return "";
+ template<class C>
+ using body_t = decltype(C::body);
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<body_t, T>::value) {
+ if constexpr (std::is_same_v<std::optional<std::string>,
+ std::remove_cv_t<decltype(e.content.body)>>)
+ return e.content.body ? e.content.body.value() : "";
+ else if constexpr (std::is_same_v<std::string,
+ std::remove_cv_t<decltype(e.content.body)>>)
+ return e.content.body;
}
+ return "";
+ }
};
struct EventFormattedBody
{
- template<class C>
- using formatted_body_t = decltype(C::formatted_body);
- template<class T>
- std::string operator()(const mtx::events::RoomEvent<T> &e)
- {
- if constexpr (is_detected<formatted_body_t, T>::value) {
- if (e.content.format == "org.matrix.custom.html")
- return e.content.formatted_body;
- }
- return "";
+ template<class C>
+ using formatted_body_t = decltype(C::formatted_body);
+ template<class T>
+ std::string operator()(const mtx::events::RoomEvent<T> &e)
+ {
+ if constexpr (is_detected<formatted_body_t, T>::value) {
+ if (e.content.format == "org.matrix.custom.html")
+ return e.content.formatted_body;
}
+ return "";
+ }
};
struct EventFile
{
- template<class Content>
- using file_t = decltype(Content::file);
- template<class T>
- std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<file_t, T>::value)
- return e.content.file;
- return std::nullopt;
- }
+ template<class Content>
+ using file_t = decltype(Content::file);
+ template<class T>
+ std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<file_t, T>::value)
+ return e.content.file;
+ return std::nullopt;
+ }
};
struct EventUrl
{
- template<class Content>
- using url_t = decltype(Content::url);
- template<class T>
- std::string operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<url_t, T>::value) {
- if (auto file = EventFile{}(e))
- return file->url;
- return e.content.url;
- }
- return "";
+ template<class Content>
+ using url_t = decltype(Content::url);
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<url_t, T>::value) {
+ if (auto file = EventFile{}(e))
+ return file->url;
+ return e.content.url;
}
+ return "";
+ }
};
struct EventThumbnailUrl
{
- template<class Content>
- using thumbnail_url_t = decltype(Content::info.thumbnail_url);
- template<class T>
- std::string operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<thumbnail_url_t, T>::value) {
- return e.content.info.thumbnail_url;
- }
- return "";
+ template<class Content>
+ using thumbnail_url_t = decltype(Content::info.thumbnail_url);
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<thumbnail_url_t, T>::value) {
+ return e.content.info.thumbnail_url;
}
+ return "";
+ }
};
struct EventBlurhash
{
- template<class Content>
- using blurhash_t = decltype(Content::info.blurhash);
- template<class T>
- std::string operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<blurhash_t, T>::value) {
- return e.content.info.blurhash;
- }
- return "";
+ template<class Content>
+ using blurhash_t = decltype(Content::info.blurhash);
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<blurhash_t, T>::value) {
+ return e.content.info.blurhash;
}
+ return "";
+ }
};
struct EventFilename
{
- template<class T>
- std::string operator()(const mtx::events::Event<T> &)
- {
- return "";
- }
- std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
- {
- // body may be the original filename
- return e.content.body;
- }
- std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
- {
- // body may be the original filename
- return e.content.body;
- }
- std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
- {
- // body may be the original filename
- return e.content.body;
- }
- std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
- {
- // body may be the original filename
- if (!e.content.filename.empty())
- return e.content.filename;
- return e.content.body;
- }
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &)
+ {
+ return "";
+ }
+ std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
+ {
+ // body may be the original filename
+ return e.content.body;
+ }
+ std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
+ {
+ // body may be the original filename
+ return e.content.body;
+ }
+ std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
+ {
+ // body may be the original filename
+ return e.content.body;
+ }
+ std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
+ {
+ // body may be the original filename
+ if (!e.content.filename.empty())
+ return e.content.filename;
+ return e.content.body;
+ }
};
struct EventMimeType
{
- template<class Content>
- using mimetype_t = decltype(Content::info.mimetype);
- template<class T>
- std::string operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<mimetype_t, T>::value) {
- return e.content.info.mimetype;
- }
- return "";
+ template<class Content>
+ using mimetype_t = decltype(Content::info.mimetype);
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<mimetype_t, T>::value) {
+ return e.content.info.mimetype;
}
+ return "";
+ }
};
struct EventFilesize
{
- template<class Content>
- using filesize_t = decltype(Content::info.size);
- template<class T>
- int64_t operator()(const mtx::events::RoomEvent<T> &e)
- {
- if constexpr (is_detected<filesize_t, T>::value) {
- return e.content.info.size;
- }
- return 0;
+ template<class Content>
+ using filesize_t = decltype(Content::info.size);
+ template<class T>
+ int64_t operator()(const mtx::events::RoomEvent<T> &e)
+ {
+ if constexpr (is_detected<filesize_t, T>::value) {
+ return e.content.info.size;
}
+ return 0;
+ }
};
struct EventRelations
{
- template<class Content>
- using related_ev_id_t = decltype(Content::relations);
- template<class T>
- mtx::common::Relations operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<related_ev_id_t, T>::value) {
- return e.content.relations;
- }
- return {};
+ template<class Content>
+ using related_ev_id_t = decltype(Content::relations);
+ template<class T>
+ mtx::common::Relations operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<related_ev_id_t, T>::value) {
+ return e.content.relations;
}
+ return {};
+ }
};
struct SetEventRelations
{
- mtx::common::Relations new_relations;
- template<class Content>
- using related_ev_id_t = decltype(Content::relations);
- template<class T>
- void operator()(mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<related_ev_id_t, T>::value) {
- e.content.relations = std::move(new_relations);
- }
+ mtx::common::Relations new_relations;
+ template<class Content>
+ using related_ev_id_t = decltype(Content::relations);
+ template<class T>
+ void operator()(mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<related_ev_id_t, T>::value) {
+ e.content.relations = std::move(new_relations);
}
+ }
};
struct EventTransactionId
{
- template<class T>
- std::string operator()(const mtx::events::RoomEvent<T> &e)
- {
- return e.unsigned_data.transaction_id;
- }
- template<class T>
- std::string operator()(const mtx::events::Event<T> &e)
- {
- return e.unsigned_data.transaction_id;
- }
+ template<class T>
+ std::string operator()(const mtx::events::RoomEvent<T> &e)
+ {
+ return e.unsigned_data.transaction_id;
+ }
+ template<class T>
+ std::string operator()(const mtx::events::Event<T> &e)
+ {
+ return e.unsigned_data.transaction_id;
+ }
};
struct EventMediaHeight
{
- template<class Content>
- using h_t = decltype(Content::info.h);
- template<class T>
- uint64_t operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<h_t, T>::value) {
- return e.content.info.h;
- }
- return -1;
+ template<class Content>
+ using h_t = decltype(Content::info.h);
+ template<class T>
+ uint64_t operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<h_t, T>::value) {
+ return e.content.info.h;
}
+ return -1;
+ }
};
struct EventMediaWidth
{
- template<class Content>
- using w_t = decltype(Content::info.w);
- template<class T>
- uint64_t operator()(const mtx::events::Event<T> &e)
- {
- if constexpr (is_detected<w_t, T>::value) {
- return e.content.info.w;
- }
- return -1;
+ template<class Content>
+ using w_t = decltype(Content::info.w);
+ template<class T>
+ uint64_t operator()(const mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<w_t, T>::value) {
+ return e.content.info.w;
}
+ return -1;
+ }
};
template<class T>
double
eventPropHeight(const mtx::events::RoomEvent<T> &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", "<br>");
+ auto formatted = formatted_body(event);
+ if (!formatted.empty())
+ return QString::fromStdString(formatted);
+ else
+ return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "<br>");
}
std::optional<mtx::crypto::EncryptedFile>
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 Default, class AlwaysVoid, template<class...> 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 Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
{
- using value_t = std::true_type;
- using type = Op<Args...>;
+ using value_t = std::true_type;
+ using type = Op<Args...>;
};
} // 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<SingleImagePackModel>(new SingleImagePackModel(pack)));
- }
+ for (const auto &pack : packs_) {
+ packs.push_back(QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack)));
+ }
}
int
ImagePackListModel::rowCount(const QModelIndex &) const
{
- return (int)packs.size();
+ return (int)packs.size();
}
QHash<int, QByteArray>
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<size_t>(row) >= packs.size())
- return {};
- auto e = packs.at(row).get();
- QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership);
- return e;
+ if (row < 0 || static_cast<size_t>(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<int, QByteArray> 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<int, QByteArray> 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<QSharedPointer<SingleImagePackModel>> packs;
+ std::vector<QSharedPointer<SingleImagePackModel>> 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<int, QByteArray>
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<int, QByteArray> 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<int, QByteArray> 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<Invitee *> invitees_;
+ QVector<Invitee *> 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<JdenticonInterface *>(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<JdenticonInterface *>(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<spdlog::sinks::rotating_file_sink_mt>(
- file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
-
- auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
-
- std::vector<spdlog::sink_ptr> sinks;
- sinks.push_back(file_sink);
- sinks.push_back(console_sink);
-
- net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
- ui_logger = std::make_shared<spdlog::logger>("ui", std::begin(sinks), std::end(sinks));
- db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
- crypto_logger =
- std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
- qml_logger = std::make_shared<spdlog::logger>("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<spdlog::sinks::rotating_file_sink_mt>(
+ file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
+
+ auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
+
+ std::vector<spdlog::sink_ptr> sinks;
+ sinks.push_back(file_sink);
+ sinks.push_back(console_sink);
+
+ net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
+ ui_logger = std::make_shared<spdlog::logger>("ui", std::begin(sinks), std::end(sinks));
+ db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
+ crypto_logger = std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
+ qml_logger = std::make_shared<spdlog::logger>("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<spdlog::logger>
ui()
{
- return ui_logger;
+ return ui_logger;
}
std::shared_ptr<spdlog::logger>
net()
{
- return net_logger;
+ return net_logger;
}
std::shared_ptr<spdlog::logger>
db()
{
- return db_logger;
+ return db_logger;
}
std::shared_ptr<spdlog::logger>
crypto()
{
- return crypto_logger;
+ return crypto_logger;
}
std::shared_ptr<spdlog::logger>
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>("LoginPage::LoginMethod");
+ qRegisterMetaType<LoginPage::LoginMethod>("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<void (LoginPage::*)(QLabel *, const QString &)>(&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<void (LoginPage::*)(QLabel *, const QString &)>(&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<User>(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<User>(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<User>(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<User>(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>(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>(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<void(const QString &room_id)> 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<void(const mtx::requests::CreateRoom &request)> 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<Qt::AlignmentFlag> 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<Qt::AlignmentFlag> 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<void(const QStringList &invitees)> callback);
- void openCreateRoomDialog(
- std::function<void(const mtx::requests::CreateRoom &request)> callback);
- void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
- void openLogoutDialog();
+ void openLeaveRoomDialog(const QString &room_id);
+ void openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback);
+ void openCreateRoomDialog(
+ std::function<void(const mtx::requests::CreateRoom &request)> callback);
+ void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
+ void openLogoutDialog();
- void hideOverlay();
- void showSolidOverlayModal(QWidget *content,
- QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
- void showTransparentOverlayModal(QWidget *content,
- QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop |
- Qt::AlignHCenter);
+ void hideOverlay();
+ void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
+ void showTransparentOverlayModal(QWidget *content,
+ QFlags<Qt::AlignmentFlag> 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> 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> 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<mtx::responses::Login>();
- qRegisterMetaType<mtx::responses::Messages>();
- qRegisterMetaType<mtx::responses::Notifications>();
- qRegisterMetaType<mtx::responses::Rooms>();
- qRegisterMetaType<mtx::responses::Sync>();
- qRegisterMetaType<mtx::responses::JoinedGroups>();
- qRegisterMetaType<mtx::responses::GroupProfile>();
- qRegisterMetaType<std::string>();
- qRegisterMetaType<nlohmann::json>();
- qRegisterMetaType<std::vector<std::string>>();
- qRegisterMetaType<std::vector<QString>>();
- qRegisterMetaType<std::map<QString, bool>>("std::map<QString, bool>");
- qRegisterMetaType<std::set<QString>>();
+ qRegisterMetaType<mtx::responses::Login>();
+ qRegisterMetaType<mtx::responses::Messages>();
+ qRegisterMetaType<mtx::responses::Notifications>();
+ qRegisterMetaType<mtx::responses::Rooms>();
+ qRegisterMetaType<mtx::responses::Sync>();
+ qRegisterMetaType<mtx::responses::JoinedGroups>();
+ qRegisterMetaType<mtx::responses::GroupProfile>();
+ qRegisterMetaType<std::string>();
+ qRegisterMetaType<nlohmann::json>();
+ qRegisterMetaType<std::vector<std::string>>();
+ qRegisterMetaType<std::vector<QString>>();
+ qRegisterMetaType<std::map<QString, bool>>("std::map<QString, bool>");
+ qRegisterMetaType<std::set<QString>>();
}
} // 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<RoomMember> &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<int, QByteArray>
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<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override
- {
- Q_UNUSED(parent)
- return static_cast<int>(m_memberList.size());
- }
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override
+ {
+ Q_UNUSED(parent)
+ return static_cast<int>(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<RoomMember> &users);
+ void addUsers(const std::vector<RoomMember> &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<QPair<RoomMember, QString>> m_memberList;
- QString room_id_;
- RoomInfo info_;
- int numUsersLoaded_{0};
- bool loadingMoreMembers_{false};
+ QVector<QPair<RoomMember, QString>> 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<QString, mtx::crypto::EncryptedFile> 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<mtx::crypto::EncryptedFile> 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<mtx::crypto::EncryptedFile> 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<void(QString, QSize, QImage, QString)> 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<void(QString, QSize, QImage, QString)> 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<std::map<std::string, mtx::events::msg::OlmCipherContent>>();
+ msg.sender = obj.at("sender");
+ msg.sender_key = obj.at("content").at("sender_key");
+ msg.ciphertext = obj.at("content")
+ .at("ciphertext")
+ .get<std::map<std::string, mtx::events::msg::OlmCipherContent>>();
}
mtx::crypto::OlmClient *
client()
{
- return client_.get();
+ return client_.get();
}
static void
handle_secret_request(const mtx::events::DeviceEvent<mtx::events::msg::SecretRequest> *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<mtx::events::msg::SecretSend> secretSend;
- secretSend.type = EventType::SecretSend;
- secretSend.content.request_id = e->content.request_id;
+ // this is a verified device
+ mtx::events::DeviceEvent<mtx::events::msg::SecretSend> 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<mtx::events::collections::DeviceEvents> &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<mtx::events::msg::KeyRequest> 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<mtx::events::msg::KeyRequest> 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<mtx::events::msg::KeyVerificationAccept>>(msg);
- ChatPage::instance()->receivedDeviceVerificationAccept(message.content);
- } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) {
- auto message = std::get<
- mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationRequest>>(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<mtx::events::msg::KeyVerificationCancel>>(msg);
- ChatPage::instance()->receivedDeviceVerificationCancel(message.content);
- } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) {
- auto message =
- std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationKey>>(
- msg);
- ChatPage::instance()->receivedDeviceVerificationKey(message.content);
- } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) {
- auto message =
- std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationMac>>(
- msg);
- ChatPage::instance()->receivedDeviceVerificationMac(message.content);
- } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) {
- auto message = std::get<
- mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationStart>>(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<mtx::events::msg::KeyVerificationReady>>(msg);
- ChatPage::instance()->receivedDeviceVerificationReady(message.content);
- } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) {
- auto message =
- std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationDone>>(
- msg);
- ChatPage::instance()->receivedDeviceVerificationDone(message.content);
- } else if (auto e =
- std::get_if<mtx::events::DeviceEvent<mtx::events::msg::SecretRequest>>(
- &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<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationAccept>>(msg);
+ ChatPage::instance()->receivedDeviceVerificationAccept(message.content);
+ } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) {
+ auto message =
+ std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationRequest>>(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<mtx::events::msg::KeyVerificationCancel>>(msg);
+ ChatPage::instance()->receivedDeviceVerificationCancel(message.content);
+ } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) {
+ auto message =
+ std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationKey>>(msg);
+ ChatPage::instance()->receivedDeviceVerificationKey(message.content);
+ } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) {
+ auto message =
+ std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationMac>>(msg);
+ ChatPage::instance()->receivedDeviceVerificationMac(message.content);
+ } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) {
+ auto message =
+ std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationStart>>(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<mtx::events::msg::KeyVerificationReady>>(msg);
+ ChatPage::instance()->receivedDeviceVerificationReady(message.content);
+ } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) {
+ auto message =
+ std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationDone>>(msg);
+ ChatPage::instance()->receivedDeviceVerificationDone(message.content);
+ } else if (auto e =
+ std::get_if<mtx::events::DeviceEvent<mtx::events::msg::SecretRequest>>(&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<mtx::events::collections::DeviceEvents> 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<DeviceEvent<msg::KeyVerificationAccept>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationAccept(e1->content);
+ } else if (auto e2 =
+ std::get_if<DeviceEvent<msg::KeyVerificationRequest>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationRequest(e2->content, e2->sender);
+ } else if (auto e3 =
+ std::get_if<DeviceEvent<msg::KeyVerificationCancel>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationCancel(e3->content);
+ } else if (auto e4 = std::get_if<DeviceEvent<msg::KeyVerificationKey>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationKey(e4->content);
+ } else if (auto e5 = std::get_if<DeviceEvent<msg::KeyVerificationMac>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationMac(e5->content);
+ } else if (auto e6 =
+ std::get_if<DeviceEvent<msg::KeyVerificationStart>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationStart(e6->content, e6->sender);
+ } else if (auto e7 =
+ std::get_if<DeviceEvent<msg::KeyVerificationReady>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationReady(e7->content);
+ } else if (auto e8 =
+ std::get_if<DeviceEvent<msg::KeyVerificationDone>>(&device_event)) {
+ ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
+ } else if (auto roomKey = std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
+ create_inbound_megolm_session(*roomKey, msg.sender_key, sender_ed25519);
+ } else if (auto forwardedRoomKey =
+ std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(&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<DeviceEvent<msg::SecretSend>>(&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<mtx::identifiers::User,
+ std::map<std::string, mtx::events::msg::SecretRequest>>
+ 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<mtx::events::collections::DeviceEvents> 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<mtx::events::msg::SecretRequest>(
+ 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<DeviceEvent<msg::KeyVerificationAccept>>(&device_event)) {
- ChatPage::instance()->receivedDeviceVerificationAccept(e1->content);
- } else if (auto e2 = std::get_if<DeviceEvent<msg::KeyVerificationRequest>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationRequest(e2->content,
- e2->sender);
- } else if (auto e3 = std::get_if<DeviceEvent<msg::KeyVerificationCancel>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationCancel(e3->content);
- } else if (auto e4 = std::get_if<DeviceEvent<msg::KeyVerificationKey>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationKey(e4->content);
- } else if (auto e5 = std::get_if<DeviceEvent<msg::KeyVerificationMac>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationMac(e5->content);
- } else if (auto e6 = std::get_if<DeviceEvent<msg::KeyVerificationStart>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationStart(e6->content,
- e6->sender);
- } else if (auto e7 = std::get_if<DeviceEvent<msg::KeyVerificationReady>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationReady(e7->content);
- } else if (auto e8 = std::get_if<DeviceEvent<msg::KeyVerificationDone>>(
- &device_event)) {
- ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
- } else if (auto roomKey =
- std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
- create_inbound_megolm_session(
- *roomKey, msg.sender_key, sender_ed25519);
- } else if (auto forwardedRoomKey =
- std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
- &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<DeviceEvent<msg::SecretSend>>(&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<std::string, mtx::events::msg::SecretRequest>>
- 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<mtx::events::msg::SecretRequest>(
- 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<DeviceEvent<msg::SecretRequest>>(&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<std::string, std::vector<std::string>> 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<DeviceEvent<msg::SecretRequest>>(&device_event)) {
+ handle_secret_request(sec_req, msg.sender);
+ }
- send_encrypted_to_device_messages(
- targets, mtx::events::DeviceEvent<mtx::events::msg::Dummy>{}, 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<std::string, std::vector<std::string>> 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<mtx::events::msg::Dummy>{}, 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<std::string, std::vector<std::string>> 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<std::string, std::vector<std::string>> 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<mtx::events::msg::RoomKey> 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<mtx::events::msg::RoomKey> 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::DeviceEvent<mtx::events::msg::R
const std::string &sender_key,
const std::string &sender_ed25519)
{
- 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);
+ 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<mtx::events::msg::ForwardedRoomKey> &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<int>(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<int>(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<mtx::events::msg::Encrypted> 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<mtx::identifiers::User, std::map<std::string, decltype(request)>> body;
- body[mtx::identifiers::parse<mtx::identifiers::User>(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<mtx::identifiers::User, std::map<std::string, decltype(request)>> body;
+ body[mtx::identifiers::parse<mtx::identifiers::User>(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<mtx::events::msg::KeyRequest> &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<mtx::events::msg::ForwardedRoomKey> room_key;
- room_key.content = payload;
- room_key.type = mtx::events::EventType::ForwardedRoomKey;
-
- std::map<std::string, std::vector<std::string>> 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<mtx::events::msg::ForwardedRoomKey> room_key;
+ room_key.content = payload;
+ room_key.type = mtx::events::EventType::ForwardedRoomKey;
+
+ std::map<std::string, std::vector<std::string>> 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<mtx::events::msg::Encrypted> &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<std::string, std::vector<std::s
const mtx::events::collections::DeviceEvents &event,
bool force_new_session)
{
- static QMap<QPair<std::string, std::string>, qint64> rateLimit;
+ static QMap<QPair<std::string, std::string>, 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<std::string, std::vector<std::string>> keysToQuery;
- mtx::requests::ClaimKeys claims;
- std::map<mtx::identifiers::User, std::map<std::string, mtx::events::msg::OlmEncrypted>>
- messages;
- std::map<std::string, std::map<std::string, DevicePublicKeys>> pks;
+ std::map<std::string, std::vector<std::string>> keysToQuery;
+ mtx::requests::ClaimKeys claims;
+ std::map<mtx::identifiers::User, std::map<std::string, mtx::events::msg::OlmEncrypted>>
+ messages;
+ std::map<std::string, std::map<std::string, DevicePublicKeys>> 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<mtx::identifiers::User>(user)][device] =
+ olm::client()
+ ->create_olm_encrypted_content(session->get(),
+ ev_json,
+ UserId(user),
+ d.keys.at("ed25519:" + device),
+ device_curve)
+ .get<mtx::events::msg::OlmEncrypted>();
+
+ 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<mtx::events::msg::OlmEncrypted>(
+ 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<mtx::identifiers::User, std::map<std::string, mtx::events::msg::OlmEncrypted>>
+ 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<mtx::identifiers::User>(user)][device] =
- olm::client()
- ->create_olm_encrypted_content(session->get(),
- ev_json,
- UserId(user),
- d.keys.at("ed25519:" + device),
- device_curve)
- .get<mtx::events::msg::OlmEncrypted>();
-
- 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<mtx::identifiers::User>(user_id)][device_id] =
+ olm::client()
+ ->create_olm_encrypted_content(
+ session.get(), ev_json, UserId(user_id), sign_key, id_key)
+ .get<mtx::events::msg::OlmEncrypted>();
+
+ 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<mtx::events::msg::OlmEncrypted>(
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<mtx::identifiers::User,
- std::map<std::string, mtx::events::msg::OlmEncrypted>>
- 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<mtx::identifiers::User>(
- user_id)][device_id] =
- olm::client()
- ->create_olm_encrypted_content(session.get(),
- ev_json,
- UserId(user_id),
- sign_key,
- id_key)
- .get<mtx::events::msg::OlmEncrypted>();
-
- 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<mtx::events::msg::OlmEncrypted>(
- 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<int>(err->status_code));
- return;
- }
-
- nhlog::net()->info("queried keys");
-
- cache::client()->updateUserKeys(cache::nextBatchToken(), res);
-
- mtx::requests::ClaimKeys claim_keys;
-
- std::map<std::string, std::map<std::string, DevicePublicKeys>> 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<int>(err->status_code));
+ return;
+ }
+
+ nhlog::net()->info("queried keys");
+
+ cache::client()->updateUserKeys(cache::nextBatchToken(), res);
+
+ mtx::requests::ClaimKeys claim_keys;
+
+ std::map<std::string, std::map<std::string, DevicePublicKeys>> 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<mtx::identifiers::User,
- std::map<std::string, mtx::events::msg::SecretRequest>>
- body;
+ std::map<mtx::identifiers::User, std::map<std::string, mtx::events::msg::SecretRequest>>
+ 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<mtx::events::msg::SecretRequest>(
+ 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<mtx::events::msg::SecretRequest>(
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<mtx::events::msg::SecretRequest>(
- 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<std::string, mtx::secret_storage::AesHmacSha2EncryptedData> &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<Secret> 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<Secret> 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<Secret> user_signing_key;
- if (!err)
- user_signing_key = secret;
-
- std::map<std::string,
- std::map<std::string, AesHmacSha2EncryptedData>>
- 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<Secret> 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<Secret> 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<Secret> user_signing_key;
+ if (!err)
+ user_signing_key = secret;
+
+ std::map<std::string, std::map<std::string, AesHmacSha2EncryptedData>>
+ 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<std::string> error_message;
- std::optional<mtx::events::collections::TimelineEvents> event;
+ DecryptionErrorCode error;
+ std::optional<std::string> error_message;
+ std::optional<mtx::events::collections::TimelineEvents> event;
};
struct OlmMessage
{
- std::string sender_key;
- std::string sender;
+ std::string sender_key;
+ std::string sender;
- using RecipientKey = std::string;
- std::map<RecipientKey, mtx::events::msg::OlmCipherContent> ciphertext;
+ using RecipientKey = std::string;
+ std::map<RecipientKey, mtx::events::msg::OlmCipherContent> 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<int, QByteArray>
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<uint64_t, std::string, std::greater<uint64_t>> &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<QString, QDateTime> 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<QString, QDateTime> 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<int, QByteArray> 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<int, QByteArray> 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<uint64_t, std::string, std::greater<uint64_t>> &users);
- void update();
+ void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users);
+ void update();
private:
- QString dateFormat(const QDateTime &then) const;
+ QString dateFormat(const QDateTime &then) const;
- QString event_id_;
- QString room_id_;
- QVector<QPair<QString, QDateTime>> readReceipts_;
+ QString event_id_;
+ QString room_id_;
+ QVector<QPair<QString, QDateTime>> 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<mtx::user_interactive::Unauthorized>();
- qRegisterMetaType<mtx::user_interactive::Auth>();
- 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<mtx::user_interactive::Unauthorized>();
+ qRegisterMetaType<mtx::user_interactive::Auth>();
+ 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<mtx::responses::Register>
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<int>(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<int>(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<int>(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<int>(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<mtx::responses::Register> 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<mtx::responses::Register> 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<int, QByteArray>
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<std::string>
RoomDirectoryModel::getViasForRoom(const std::vector<std::string> &aliases)
{
- std::vector<std::string> 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<std::string> 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<size_t>(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<size_t>(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<mtx::responses::PublicRoomsChunk> 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<int>(publicRoomsData_.size()),
- static_cast<int>(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<int>(publicRoomsData_.size()),
+ static_cast<int>(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<int, QByteArray> roleNames() const override;
+ enum Roles
+ {
+ Name = Qt::UserRole,
+ Id,
+ AvatarUrl,
+ Topic,
+ MemberCount,
+ Previewable,
+ CanJoin,
+ };
+ QHash<int, QByteArray> 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<int>(publicRoomsData_.size());
- }
+ inline int rowCount(const QModelIndex &parent = QModelIndex()) const override
+ {
+ (void)parent;
+ return static_cast<int>(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<mtx::responses::PublicRoomsChunk> rooms,
- const std::string &next_batch);
- void loadingMoreRoomsChanged();
- void reachedEndOfPaginationChanged();
+ void fetchedRoomsBatch(std::vector<mtx::responses::PublicRoomsChunk> 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<mtx::responses::PublicRoomsChunk> rooms,
- const std::string &next_batch);
+ void displayRooms(std::vector<mtx::responses::PublicRoomsChunk> 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<mtx::responses::PublicRoomsChunk> publicRoomsData_;
+ std::string server_;
+ std::string userSearchString_;
+ std::string prevBatch_;
+ std::string nextBatch_;
+ bool canFetchMore_{true};
+ bool loadingMoreRooms_{false};
+ bool reachedEndOfPagination_{false};
+ std::vector<mtx::responses::PublicRoomsChunk> publicRoomsData_;
- std::vector<std::string> getViasForRoom(const std::vector<std::string> &room);
- void resetDisplayedData();
+ std::vector<std::string> getViasForRoom(const std::vector<std::string> &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<std::string> rooms_ = cache::joinedRooms();
- roomInfos = cache::getRoomInfo(rooms_);
- if (!showOnlyRoomWithAliases_) {
- roomids.reserve(rooms_.size());
- roomAliases.reserve(rooms_.size());
- }
+ std::vector<std::string> 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<int, QByteArray>
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<int, QByteArray> 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<int, QByteArray> 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<QString> roomids;
- std::vector<QString> roomAliases;
- std::map<QString, RoomInfo> roomInfos;
- bool showOnlyRoomWithAliases_;
+ std::vector<QString> roomids;
+ std::vector<QString> roomAliases;
+ std::map<QString, RoomInfo> 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<mtx::common::ImageInfo>();
+ [[maybe_unused]] static auto imageInfoType = qRegisterMetaType<mtx::common::ImageInfo>();
- 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<int, QByteArray>
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<mtx::events::msc2545::ImagePackRooms>>(
- &*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<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
+ &*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<mtx::events::msc2545::ImagePackRooms>>(
- &*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<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
+ &*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<QUrl> 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<int>(shortcodes.size()), static_cast<int>(shortcodes.size()));
+ mtx::events::msc2545::PackImage img{};
+ img.url = uri;
+ img.info = info;
+ beginInsertRows(
+ QModelIndex(), static_cast<int>(shortcodes.size()), static_cast<int>(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<int, QByteArray> 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<QUrl> 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<int, QByteArray> 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<QUrl> 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<std::string> shortcodes;
+ mtx::events::msc2545::ImagePack pack;
+ std::vector<std::string> 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<QSize>
MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const
{
- Q_UNUSED(mode);
- Q_UNUSED(state);
- QList<QSize> 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<QSize> 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<MsgCountComposedIcon *>(icon_->clone());
- tmp->msgCount = count;
+ // Custom drawing on Linux.
+ MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(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<QSize> 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<QSize> 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> UserSettings::instance_;
UserSettings::UserSettings()
{
- connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() {
- instance_.clear();
- });
+ connect(
+ QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); });
}
QSharedPointer<UserSettings>
UserSettings::instance()
{
- return instance_;
+ return instance_;
}
void
UserSettings::initialize(std::optional<QString> profile)
{
- instance_.reset(new UserSettings());
- instance_->load(profile);
+ instance_.reset(new UserSettings());
+ instance_->load(profile);
}
void
UserSettings::load(std::optional<QString> 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<Presence>().keyToValue(tempPresence.c_str());
- if (presenceValue < 0)
- presenceValue = 0;
- presence_ = static_cast<Presence>(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<Presence>().keyToValue(tempPresence.c_str());
+ if (presenceValue < 0)
+ presenceValue = 0;
+ presence_ = static_cast<Presence>(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 *>(QApplication::instance())->setStyleSheet(stylesheet);
+ qobject_cast<QApplication *>(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<Presence>().valueToKey(
- static_cast<int>(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<Presence>().valueToKey(static_cast<int>(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<UserSettings> 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<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &text) {
- settings_->setTheme(text.toLower());
- emit themeChanged();
- });
- connect(scaleFactorCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); });
- connect(fontSizeCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); });
- connect(fontSelectionCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &family) { settings_->setFontFamily(family.trimmed()); });
- connect(emojiFontSelectionCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
-
- connect(ringtoneCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&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<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString µphone) { settings_->setMicrophone(microphone); });
-
- connect(cameraCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &camera) {
- settings_->setCamera(camera);
- std::vector<std::string> resolutions =
- CallDevices::instance().resolutions(camera.toStdString());
- cameraResolutionCombo_->clear();
- for (const auto &resolution : resolutions)
- cameraResolutionCombo_->addItem(QString::fromStdString(resolution));
- });
-
- connect(cameraResolutionCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &resolution) {
- settings_->setCameraResolution(resolution);
- std::vector<std::string> 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<void (QComboBox::*)(const QString &)>(&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<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &text) {
+ settings_->setTheme(text.toLower());
+ emit themeChanged();
+ });
+ connect(scaleFactorCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); });
+ connect(fontSizeCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); });
+ connect(fontSelectionCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &family) { settings_->setFontFamily(family.trimmed()); });
+ connect(emojiFontSelectionCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); });
+
+ connect(ringtoneCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&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<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString µphone) { settings_->setMicrophone(microphone); });
+
+ connect(cameraCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &camera) {
+ settings_->setCamera(camera);
+ std::vector<std::string> resolutions =
+ CallDevices::instance().resolutions(camera.toStdString());
+ cameraResolutionCombo_->clear();
+ for (const auto &resolution : resolutions)
+ cameraResolutionCombo_->addItem(QString::fromStdString(resolution));
+ });
+
+ connect(cameraResolutionCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &resolution) {
+ settings_->setCameraResolution(resolution);
+ std::vector<std::string> 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<void (QComboBox::*)(const QString &)>(&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<int>(&QSpinBox::valueChanged),
- this,
- [this](int newValue) { settings_->setTimelineMaxWidth(newValue); });
+ connect(timelineMaxWidthSpin_,
+ qOverload<int>(&QSpinBox::valueChanged),
+ this,
+ [this](int newValue) { settings_->setTimelineMaxWidth(newValue); });
- connect(privacyScreenTimeout_,
- qOverload<int>(&QSpinBox::valueChanged),
- this,
- [this](int newValue) { settings_->setPrivacyScreenTimeout(newValue); });
+ connect(privacyScreenTimeout_,
+ qOverload<int>(&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<UserSettings> instance();
- static void initialize(std::optional<QString> profile);
-
- QSettings *qsettings() { return &settings; }
-
- enum class Presence
- {
- AutomaticPresence,
- Online,
- Unavailable,
- Offline,
- };
- Q_ENUM(Presence)
-
- void save();
- void load(std::optional<QString> 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<UserSettings> instance();
+ static void initialize(std::optional<QString> profile);
+
+ QSettings *qsettings() { return &settings; }
+
+ enum class Presence
+ {
+ AutomaticPresence,
+ Online,
+ Unavailable,
+ Offline,
+ };
+ Q_ENUM(Presence)
+
+ void save();
+ void load(std::optional<QString> 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<UserSettings> 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<UserSettings> 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<UserSettings> settings, QWidget *parent = nullptr);
+ UserSettingsPage(QSharedPointer<UserSettings> 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<UserSettings> 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<UserSettings> 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<int, QByteArray>
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<int, QByteArray> 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<int, QByteArray> 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<std::string> roomMembers_;
- std::vector<QString> displayNames;
- std::vector<QString> userids;
+ std::string room_id;
+ std::vector<std::string> roomMembers_;
+ std::vector<QString> displayNames;
+ std::vector<QString> 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<class T, class Event>
static DescInfo
createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName)
{
- const auto msg = std::get<T>(event);
- const auto sender = QString::fromStdString(msg.sender);
+ const auto msg = std::get<T>(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<T>(username, body, sender == localUser),
- utils::descriptiveTime(ts),
- msg.origin_server_ts,
- ts};
+ return DescInfo{QString::fromStdString(msg.event_id),
+ sender,
+ utils::messageDescription<T>(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("<mx-reply>.*</mx-reply>",
- QRegularExpression::DotMatchesEverythingOption));
- formatted_body.replace("@room", QString::fromUtf8("@\u2060room"));
- return formatted_body.toStdString();
+ QString formatted_body = QString::fromStdString(formatted_bodyi);
+ formatted_body.remove(QRegularExpression("<mx-reply>.*</mx-reply>",
+ 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<uint> utf32_string = body.toUcs4();
-
- bool insideFontBlock = false;
- for (auto &code : utf32_string) {
- if (utils::codepointIsEmoji(code)) {
- if (!insideFontBlock) {
- fmtBody += QStringLiteral("<font face=\"") %
- UserSettings::instance()->emojiFont() %
- QStringLiteral("\">");
- insideFontBlock = true;
- }
-
- } else {
- if (insideFontBlock) {
- fmtBody += QStringLiteral("</font>");
- insideFontBlock = false;
- }
- }
- if (QChar::requiresSurrogates(code)) {
- QChar emoji[] = {static_cast<ushort>(QChar::highSurrogate(code)),
- static_cast<ushort>(QChar::lowSurrogate(code))};
- fmtBody.append(emoji, 2);
- } else {
- fmtBody.append(QChar(static_cast<ushort>(code)));
- }
- }
- if (insideFontBlock) {
+ QString fmtBody;
+ fmtBody.reserve(body.size());
+
+ QVector<uint> utf32_string = body.toUcs4();
+
+ bool insideFontBlock = false;
+ for (auto &code : utf32_string) {
+ if (utils::codepointIsEmoji(code)) {
+ if (!insideFontBlock) {
+ fmtBody += QStringLiteral("<font face=\"") % UserSettings::instance()->emojiFont() %
+ QStringLiteral("\">");
+ insideFontBlock = true;
+ }
+
+ } else {
+ if (insideFontBlock) {
fmtBody += QStringLiteral("</font>");
+ insideFontBlock = false;
+ }
}
+ if (QChar::requiresSurrogates(code)) {
+ QChar emoji[] = {static_cast<ushort>(QChar::highSurrogate(code)),
+ static_cast<ushort>(QChar::lowSurrogate(code))};
+ fmtBody.append(emoji, 2);
+ } else {
+ fmtBody.append(QChar(static_cast<ushort>(code)));
+ }
+ }
+ if (insideFontBlock) {
+ fmtBody += QStringLiteral("</font>");
+ }
- 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<mtx::events::msg::Audio>;
- using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
- using File = mtx::events::RoomEvent<mtx::events::msg::File>;
- using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
- using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
- using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
- using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
- using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
- using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
- using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
- using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
-
- if (std::holds_alternative<Audio>(event)) {
- return createDescriptionInfo<Audio>(event, localUser, displayName);
- } else if (std::holds_alternative<Emote>(event)) {
- return createDescriptionInfo<Emote>(event, localUser, displayName);
- } else if (std::holds_alternative<File>(event)) {
- return createDescriptionInfo<File>(event, localUser, displayName);
- } else if (std::holds_alternative<Image>(event)) {
- return createDescriptionInfo<Image>(event, localUser, displayName);
- } else if (std::holds_alternative<Notice>(event)) {
- return createDescriptionInfo<Notice>(event, localUser, displayName);
- } else if (std::holds_alternative<Text>(event)) {
- return createDescriptionInfo<Text>(event, localUser, displayName);
- } else if (std::holds_alternative<Video>(event)) {
- return createDescriptionInfo<Video>(event, localUser, displayName);
- } else if (std::holds_alternative<CallInvite>(event)) {
- return createDescriptionInfo<CallInvite>(event, localUser, displayName);
- } else if (std::holds_alternative<CallAnswer>(event)) {
- return createDescriptionInfo<CallAnswer>(event, localUser, displayName);
- } else if (std::holds_alternative<CallHangUp>(event)) {
- return createDescriptionInfo<CallHangUp>(event, localUser, displayName);
- } else if (std::holds_alternative<mtx::events::Sticker>(event)) {
- return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName);
- } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) {
- const auto sender = QString::fromStdString(msg->sender);
-
- const auto username = displayName;
- const auto ts = QDateTime::fromMSecsSinceEpoch(msg->origin_server_ts);
-
- DescInfo info;
- info.userid = sender;
- info.body = QString(" %1").arg(
- messageDescription<Encrypted>(username, "", sender == localUser));
- info.timestamp = msg->origin_server_ts;
- info.descriptiveTime = utils::descriptiveTime(ts);
- info.event_id = QString::fromStdString(msg->event_id);
- info.datetime = ts;
-
- return info;
- }
+ using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
+ using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
+ using File = mtx::events::RoomEvent<mtx::events::msg::File>;
+ using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
+ using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
+ using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
+ using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
+ using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
+ using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
+ using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
+ using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
+
+ if (std::holds_alternative<Audio>(event)) {
+ return createDescriptionInfo<Audio>(event, localUser, displayName);
+ } else if (std::holds_alternative<Emote>(event)) {
+ return createDescriptionInfo<Emote>(event, localUser, displayName);
+ } else if (std::holds_alternative<File>(event)) {
+ return createDescriptionInfo<File>(event, localUser, displayName);
+ } else if (std::holds_alternative<Image>(event)) {
+ return createDescriptionInfo<Image>(event, localUser, displayName);
+ } else if (std::holds_alternative<Notice>(event)) {
+ return createDescriptionInfo<Notice>(event, localUser, displayName);
+ } else if (std::holds_alternative<Text>(event)) {
+ return createDescriptionInfo<Text>(event, localUser, displayName);
+ } else if (std::holds_alternative<Video>(event)) {
+ return createDescriptionInfo<Video>(event, localUser, displayName);
+ } else if (std::holds_alternative<CallInvite>(event)) {
+ return createDescriptionInfo<CallInvite>(event, localUser, displayName);
+ } else if (std::holds_alternative<CallAnswer>(event)) {
+ return createDescriptionInfo<CallAnswer>(event, localUser, displayName);
+ } else if (std::holds_alternative<CallHangUp>(event)) {
+ return createDescriptionInfo<CallHangUp>(event, localUser, displayName);
+ } else if (std::holds_alternative<mtx::events::Sticker>(event)) {
+ return createDescriptionInfo<mtx::events::Sticker>(event, localUser, displayName);
+ } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) {
+ const auto sender = QString::fromStdString(msg->sender);
+
+ const auto username = displayName;
+ const auto ts = QDateTime::fromMSecsSinceEpoch(msg->origin_server_ts);
+
+ DescInfo info;
+ info.userid = sender;
+ info.body =
+ QString(" %1").arg(messageDescription<Encrypted>(username, "", sender == localUser));
+ info.timestamp = msg->origin_server_ts;
+ info.descriptiveTime = utils::descriptiveTime(ts);
+ info.event_id = QString::fromStdString(msg->event_id);
+ info.datetime = ts;
+
+ return info;
+ }
- return DescInfo{};
+ return DescInfo{};
}
QString
utils::firstChar(const QString &input)
{
- if (input.isEmpty())
- return input;
+ if (input.isEmpty())
+ return input;
- for (auto const &c : input.toUcs4()) {
- if (QString::fromUcs4(&c, 1) != QString("#"))
- return QString::fromUcs4(&c, 1).toUpper();
- }
+ for (auto const &c : input.toUcs4()) {
+ if (QString::fromUcs4(&c, 1) != QString("#"))
+ return QString::fromUcs4(&c, 1).toUpper();
+ }
- return QString::fromUcs4(&input.toUcs4().at(0), 1).toUpper();
+ return QString::fromUcs4(&input.toUcs4().at(0), 1).toUpper();
}
QString
utils::humanReadableFileSize(uint64_t bytes)
{
- constexpr static const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
- constexpr static const int length = sizeof(units) / sizeof(units[0]);
+ constexpr static const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
+ constexpr static const int length = sizeof(units) / sizeof(units[0]);
- int u = 0;
- double size = static_cast<double>(bytes);
- while (size >= 1024.0 && u < length) {
- ++u;
- size /= 1024.0;
- }
+ int u = 0;
+ double size = static_cast<double>(bytes);
+ while (size >= 1024.0 && u < length) {
+ ++u;
+ size /= 1024.0;
+ }
- return QString::number(size, 'g', 4) + ' ' + units[u];
+ return QString::number(size, 'g', 4) + ' ' + units[u];
}
int
utils::levenshtein_distance(const std::string &s1, const std::string &s2)
{
- const auto nlen = s1.size();
- const auto hlen = s2.size();
+ const auto nlen = s1.size();
+ const auto hlen = s2.size();
- if (hlen == 0)
- return -1;
- if (nlen == 1)
- return (int)s2.find(s1);
+ if (hlen == 0)
+ return -1;
+ if (nlen == 1)
+ return (int)s2.find(s1);
- std::vector<int> row1(hlen + 1, 0);
+ std::vector<int> row1(hlen + 1, 0);
- for (size_t i = 0; i < nlen; ++i) {
- std::vector<int> row2(1, (int)i + 1);
-
- for (size_t j = 0; j < hlen; ++j) {
- const int cost = s1[i] != s2[j];
- row2.push_back(
- std::min(row1[j + 1] + 1, std::min(row2[j] + 1, row1[j] + cost)));
- }
+ for (size_t i = 0; i < nlen; ++i) {
+ std::vector<int> row2(1, (int)i + 1);
- row1.swap(row2);
+ for (size_t j = 0; j < hlen; ++j) {
+ const int cost = s1[i] != s2[j];
+ row2.push_back(std::min(row1[j + 1] + 1, std::min(row2[j] + 1, row1[j] + cost)));
}
- return *std::min_element(row1.begin(), row1.end());
+ row1.swap(row2);
+ }
+
+ return *std::min_element(row1.begin(), row1.end());
}
QString
utils::event_body(const mtx::events::collections::TimelineEvents &e)
{
- using namespace mtx::events;
- if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
- if (auto ev = std::get_if<RoomEvent<msg::Emote>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
- if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
- if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
- if (auto ev = std::get_if<RoomEvent<msg::Notice>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
- if (auto ev = std::get_if<RoomEvent<msg::Text>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
- if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr)
- return QString::fromStdString(ev->content.body);
-
- return "";
+ using namespace mtx::events;
+ if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+ if (auto ev = std::get_if<RoomEvent<msg::Emote>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+ if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+ if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+ if (auto ev = std::get_if<RoomEvent<msg::Notice>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+ if (auto ev = std::get_if<RoomEvent<msg::Text>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+ if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr)
+ return QString::fromStdString(ev->content.body);
+
+ return "";
}
QPixmap
utils::scaleImageToPixmap(const QImage &img, int size)
{
- if (img.isNull())
- return QPixmap();
+ if (img.isNull())
+ return QPixmap();
- // Deprecated in 5.13: const double sz =
- // std::ceil(QApplication::desktop()->screen()->devicePixelRatioF() * (double)size);
- const double sz =
- std::ceil(QGuiApplication::primaryScreen()->devicePixelRatio() * (double)size);
- return QPixmap::fromImage(
- img.scaled(sz, sz, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ // Deprecated in 5.13: const double sz =
+ // std::ceil(QApplication::desktop()->screen()->devicePixelRatioF() * (double)size);
+ const double sz =
+ std::ceil(QGuiApplication::primaryScreen()->devicePixelRatio() * (double)size);
+ return QPixmap::fromImage(img.scaled(sz, sz, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
QPixmap
utils::scaleDown(uint64_t maxWidth, uint64_t maxHeight, const QPixmap &source)
{
- if (source.isNull())
- return QPixmap();
+ if (source.isNull())
+ return QPixmap();
- const double widthRatio = (double)maxWidth / (double)source.width();
- const double heightRatio = (double)maxHeight / (double)source.height();
- const double minAspectRatio = std::min(widthRatio, heightRatio);
+ const double widthRatio = (double)maxWidth / (double)source.width();
+ const double heightRatio = (double)maxHeight / (double)source.height();
+ const double minAspectRatio = std::min(widthRatio, heightRatio);
- // Size of the output image.
- int w, h = 0;
+ // Size of the output image.
+ int w, h = 0;
- if (minAspectRatio > 1) {
- w = source.width();
- h = source.height();
- } else {
- w = source.width() * minAspectRatio;
- h = source.height() * minAspectRatio;
- }
+ if (minAspectRatio > 1) {
+ w = source.width();
+ h = source.height();
+ } else {
+ w = source.width() * minAspectRatio;
+ h = source.height() * minAspectRatio;
+ }
- return source.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ return source.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QString
utils::mxcToHttp(const QUrl &url, const QString &server, int port)
{
- auto mxcParts = mtx::client::utils::parse_mxc_url(url.toString().toStdString());
+ auto mxcParts = mtx::client::utils::parse_mxc_url(url.toString().toStdString());
- return QString("https://%1:%2/_matrix/media/r0/download/%3/%4")
- .arg(server)
- .arg(port)
- .arg(QString::fromStdString(mxcParts.server))
- .arg(QString::fromStdString(mxcParts.media_id));
+ return QString("https://%1:%2/_matrix/media/r0/download/%3/%4")
+ .arg(server)
+ .arg(port)
+ .arg(QString::fromStdString(mxcParts.server))
+ .arg(QString::fromStdString(mxcParts.media_id));
}
QString
utils::humanReadableFingerprint(const std::string &ed25519)
{
- return humanReadableFingerprint(QString::fromStdString(ed25519));
+ return humanReadableFingerprint(QString::fromStdString(ed25519));
}
QString
utils::humanReadableFingerprint(const QString &ed25519)
{
- QString fingerprint;
- for (int i = 0; i < ed25519.length(); i = i + 4) {
- fingerprint.append(ed25519.midRef(i, 4));
- if (i > 0 && i % 16 == 12)
- fingerprint.append('\n');
- else if (i < ed25519.length())
- fingerprint.append(' ');
- }
- return fingerprint;
+ QString fingerprint;
+ for (int i = 0; i < ed25519.length(); i = i + 4) {
+ fingerprint.append(ed25519.midRef(i, 4));
+ if (i > 0 && i % 16 == 12)
+ fingerprint.append('\n');
+ else if (i < ed25519.length())
+ fingerprint.append(' ');
+ }
+ return fingerprint;
}
QString
utils::linkifyMessage(const QString &body)
{
- // Convert to valid XML.
- auto doc = body;
- doc.replace(conf::strings::url_regex, conf::strings::url_html);
- doc.replace(QRegularExpression("\\b(?<![\"'])(?>(matrix:[\\S]{5,}))(?![\"'])\\b"),
- conf::strings::url_html);
+ // Convert to valid XML.
+ auto doc = body;
+ doc.replace(conf::strings::url_regex, conf::strings::url_html);
+ doc.replace(QRegularExpression("\\b(?<![\"'])(?>(matrix:[\\S]{5,}))(?![\"'])\\b"),
+ conf::strings::url_html);
- return doc;
+ return doc;
}
QString
utils::escapeBlacklistedHtml(const QString &rawStr)
{
- static const std::array allowedTags = {
- "font", "/font", "del", "/del", "h1", "/h1", "h2", "/h2",
- "h3", "/h3", "h4", "/h4", "h5", "/h5", "h6", "/h6",
- "blockquote", "/blockquote", "p", "/p", "a", "/a", "ul", "/ul",
- "ol", "/ol", "sup", "/sup", "sub", "/sub", "li", "/li",
- "b", "/b", "i", "/i", "u", "/u", "strong", "/strong",
- "em", "/em", "strike", "/strike", "code", "/code", "hr", "/hr",
- "br", "br/", "div", "/div", "table", "/table", "thead", "/thead",
- "tbody", "/tbody", "tr", "/tr", "th", "/th", "td", "/td",
- "caption", "/caption", "pre", "/pre", "span", "/span", "img", "/img"};
- QByteArray data = rawStr.toUtf8();
- QByteArray buffer;
- const int length = data.size();
- buffer.reserve(length);
- bool escapingTag = false;
- for (int pos = 0; pos != length; ++pos) {
- switch (data.at(pos)) {
- case '<': {
- bool oneTagMatched = false;
- const int endPos =
- static_cast<int>(std::min(static_cast<size_t>(data.indexOf('>', pos)),
- static_cast<size_t>(data.indexOf(' ', pos))));
-
- auto mid = data.mid(pos + 1, endPos - pos - 1);
- for (const auto &tag : allowedTags) {
- // TODO: Check src and href attribute
- if (mid.toLower() == tag) {
- oneTagMatched = true;
- }
- }
- if (oneTagMatched)
- buffer.append('<');
- else {
- escapingTag = true;
- buffer.append("<");
- }
- break;
- }
- case '>':
- if (escapingTag) {
- buffer.append(">");
- escapingTag = false;
- } else
- buffer.append('>');
- break;
- default:
- buffer.append(data.at(pos));
- break;
+ static const std::array allowedTags = {
+ "font", "/font", "del", "/del", "h1", "/h1", "h2", "/h2",
+ "h3", "/h3", "h4", "/h4", "h5", "/h5", "h6", "/h6",
+ "blockquote", "/blockquote", "p", "/p", "a", "/a", "ul", "/ul",
+ "ol", "/ol", "sup", "/sup", "sub", "/sub", "li", "/li",
+ "b", "/b", "i", "/i", "u", "/u", "strong", "/strong",
+ "em", "/em", "strike", "/strike", "code", "/code", "hr", "/hr",
+ "br", "br/", "div", "/div", "table", "/table", "thead", "/thead",
+ "tbody", "/tbody", "tr", "/tr", "th", "/th", "td", "/td",
+ "caption", "/caption", "pre", "/pre", "span", "/span", "img", "/img"};
+ QByteArray data = rawStr.toUtf8();
+ QByteArray buffer;
+ const int length = data.size();
+ buffer.reserve(length);
+ bool escapingTag = false;
+ for (int pos = 0; pos != length; ++pos) {
+ switch (data.at(pos)) {
+ case '<': {
+ bool oneTagMatched = false;
+ const int endPos =
+ static_cast<int>(std::min(static_cast<size_t>(data.indexOf('>', pos)),
+ static_cast<size_t>(data.indexOf(' ', pos))));
+
+ auto mid = data.mid(pos + 1, endPos - pos - 1);
+ for (const auto &tag : allowedTags) {
+ // TODO: Check src and href attribute
+ if (mid.toLower() == tag) {
+ oneTagMatched = true;
}
+ }
+ if (oneTagMatched)
+ buffer.append('<');
+ else {
+ escapingTag = true;
+ buffer.append("<");
+ }
+ break;
}
- return QString::fromUtf8(buffer);
+ case '>':
+ if (escapingTag) {
+ buffer.append(">");
+ escapingTag = false;
+ } else
+ buffer.append('>');
+ break;
+ default:
+ buffer.append(data.at(pos));
+ break;
+ }
+ }
+ return QString::fromUtf8(buffer);
}
QString
utils::markdownToHtml(const QString &text, bool rainbowify)
{
- const auto str = text.toUtf8();
- cmark_node *const node =
- cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE);
-
- if (rainbowify) {
- // create iterator over node
- cmark_iter *iter = cmark_iter_new(node);
-
- cmark_event_type ev_type;
-
- // First loop to get total text length
- int textLen = 0;
- while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
- cmark_node *cur = cmark_iter_get_node(iter);
- // only text nodes (no code or semilar)
- if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
- continue;
- // count up by length of current node's text
- QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
- QString(cmark_node_get_literal(cur)));
- while (tbf.toNextBoundary() != -1)
- textLen++;
- }
+ const auto str = text.toUtf8();
+ cmark_node *const node = cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE);
+
+ if (rainbowify) {
+ // create iterator over node
+ cmark_iter *iter = cmark_iter_new(node);
+
+ cmark_event_type ev_type;
+
+ // First loop to get total text length
+ int textLen = 0;
+ while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+ cmark_node *cur = cmark_iter_get_node(iter);
+ // only text nodes (no code or semilar)
+ if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
+ continue;
+ // count up by length of current node's text
+ QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
+ QString(cmark_node_get_literal(cur)));
+ while (tbf.toNextBoundary() != -1)
+ textLen++;
+ }
- // create new iter to start over
- cmark_iter_free(iter);
- iter = cmark_iter_new(node);
-
- // Second loop to rainbowify
- int charIdx = 0;
- while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
- cmark_node *cur = cmark_iter_get_node(iter);
- // only text nodes (no code or semilar)
- if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
- continue;
-
- // get text in current node
- QString nodeText(cmark_node_get_literal(cur));
- // create buffer to append rainbow text to
- QString buf;
- int boundaryStart = 0;
- int boundaryEnd = 0;
- // use QTextBoundaryFinder to iterate ofer graphemes
- QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
- nodeText);
- while ((boundaryEnd = tbf.toNextBoundary()) != -1) {
- charIdx++;
- // Split text to get current char
- auto curChar =
- nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart);
- boundaryStart = boundaryEnd;
- // Don't rainbowify whitespaces
- if (curChar.trimmed().isEmpty() ||
- codepointIsEmoji(curChar.toUcs4().first())) {
- buf.append(curChar);
- continue;
- }
-
- // get correct color for char index
- // Use colors as described here:
- // https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html
- auto color =
- QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5);
- // format color for HTML
- auto colorString = color.name(QColor::NameFormat::HexRgb);
- // create HTML element for current char
- auto curCharColored = QString("<font color=\"%0\">%1</font>")
- .arg(colorString)
- .arg(curChar);
- // append colored HTML element to buffer
- buf.append(curCharColored);
- }
-
- // create HTML_INLINE node to prevent HTML from being escaped
- auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE);
- // set content of HTML node to buffer contents
- cmark_node_set_literal(htmlNode, buf.toUtf8().data());
- // replace current node with HTML node
- cmark_node_replace(cur, htmlNode);
- // free memory of old node
- cmark_node_free(cur);
+ // create new iter to start over
+ cmark_iter_free(iter);
+ iter = cmark_iter_new(node);
+
+ // Second loop to rainbowify
+ int charIdx = 0;
+ while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+ cmark_node *cur = cmark_iter_get_node(iter);
+ // only text nodes (no code or semilar)
+ if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
+ continue;
+
+ // get text in current node
+ QString nodeText(cmark_node_get_literal(cur));
+ // create buffer to append rainbow text to
+ QString buf;
+ int boundaryStart = 0;
+ int boundaryEnd = 0;
+ // use QTextBoundaryFinder to iterate ofer graphemes
+ QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, nodeText);
+ while ((boundaryEnd = tbf.toNextBoundary()) != -1) {
+ charIdx++;
+ // Split text to get current char
+ auto curChar = nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart);
+ boundaryStart = boundaryEnd;
+ // Don't rainbowify whitespaces
+ if (curChar.trimmed().isEmpty() || codepointIsEmoji(curChar.toUcs4().first())) {
+ buf.append(curChar);
+ continue;
}
- cmark_iter_free(iter);
+ // get correct color for char index
+ // Use colors as described here:
+ // https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html
+ auto color = QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5);
+ // format color for HTML
+ auto colorString = color.name(QColor::NameFormat::HexRgb);
+ // create HTML element for current char
+ auto curCharColored =
+ QString("<font color=\"%0\">%1</font>").arg(colorString).arg(curChar);
+ // append colored HTML element to buffer
+ buf.append(curCharColored);
+ }
+
+ // create HTML_INLINE node to prevent HTML from being escaped
+ auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE);
+ // set content of HTML node to buffer contents
+ cmark_node_set_literal(htmlNode, buf.toUtf8().data());
+ // replace current node with HTML node
+ cmark_node_replace(cur, htmlNode);
+ // free memory of old node
+ cmark_node_free(cur);
}
- const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE);
- // Copy the null terminated output buffer.
- std::string html(tmp_buf);
+ cmark_iter_free(iter);
+ }
- // The buffer is no longer needed.
- free((char *)tmp_buf);
+ const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE);
+ // Copy the null terminated output buffer.
+ std::string html(tmp_buf);
- auto result = linkifyMessage(escapeBlacklistedHtml(QString::fromStdString(html))).trimmed();
+ // The buffer is no longer needed.
+ free((char *)tmp_buf);
- if (result.count("<p>") == 1 && result.startsWith("<p>") && result.endsWith("</p>")) {
- result = result.mid(3, result.size() - 3 - 4);
- }
+ auto result = linkifyMessage(escapeBlacklistedHtml(QString::fromStdString(html))).trimmed();
- return result;
-}
+ if (result.count("<p>") == 1 && result.startsWith("<p>") && result.endsWith("</p>")) {
+ result = result.mid(3, result.size() - 3 - 4);
+ }
-QString
-utils::getFormattedQuoteBody(const RelatedInfo &related, const QString &html)
-{
- auto getFormattedBody = [related]() -> QString {
- using MsgType = mtx::events::MessageType;
-
- switch (related.type) {
- case MsgType::File: {
- return "sent a file.";
- }
- case MsgType::Image: {
- return "sent an image.";
- }
- case MsgType::Audio: {
- return "sent an audio file.";
- }
- case MsgType::Video: {
- return "sent a video";
- }
- default: {
- return related.quoted_formatted_body;
- }
- }
- };
- return QString("<mx-reply><blockquote><a "
- "href=\"https://matrix.to/#/%1/%2\">In reply "
- "to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br"
- "/>%5</blockquote></mx-reply>")
- .arg(related.room,
- QString::fromStdString(related.related_event),
- related.quoted_user,
- related.quoted_user,
- getFormattedBody()) +
- html;
+ return result;
}
QString
-utils::getQuoteBody(const RelatedInfo &related)
+utils::getFormattedQuoteBody(const RelatedInfo &related, const QString &html)
{
+ auto getFormattedBody = [related]() -> QString {
using MsgType = mtx::events::MessageType;
switch (related.type) {
case MsgType::File: {
- return "sent a file.";
+ return "sent a file.";
}
case MsgType::Image: {
- return "sent an image.";
+ return "sent an image.";
}
case MsgType::Audio: {
- return "sent an audio file.";
+ return "sent an audio file.";
}
case MsgType::Video: {
- return "sent a video";
+ return "sent a video";
}
default: {
- return related.quoted_body;
+ return related.quoted_formatted_body;
}
}
+ };
+ return QString("<mx-reply><blockquote><a "
+ "href=\"https://matrix.to/#/%1/%2\">In reply "
+ "to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br"
+ "/>%5</blockquote></mx-reply>")
+ .arg(related.room,
+ QString::fromStdString(related.related_event),
+ related.quoted_user,
+ related.quoted_user,
+ getFormattedBody()) +
+ html;
+}
+
+QString
+utils::getQuoteBody(const RelatedInfo &related)
+{
+ using MsgType = mtx::events::MessageType;
+
+ switch (related.type) {
+ case MsgType::File: {
+ return "sent a file.";
+ }
+ case MsgType::Image: {
+ return "sent an image.";
+ }
+ case MsgType::Audio: {
+ return "sent an audio file.";
+ }
+ case MsgType::Video: {
+ return "sent a video";
+ }
+ default: {
+ return related.quoted_body;
+ }
+ }
}
QString
utils::linkColor()
{
- const auto theme = UserSettings::instance()->theme();
+ const auto theme = UserSettings::instance()->theme();
- if (theme == "light") {
- return "#0077b5";
- } else if (theme == "dark") {
- return "#38A3D8";
- } else {
- return QPalette().color(QPalette::Link).name();
- }
+ if (theme == "light") {
+ return "#0077b5";
+ } else if (theme == "dark") {
+ return "#38A3D8";
+ } else {
+ return QPalette().color(QPalette::Link).name();
+ }
}
uint32_t
utils::hashQString(const QString &input)
{
- uint32_t hash = 0;
+ uint32_t hash = 0;
- for (int i = 0; i < input.length(); i++) {
- hash = input.at(i).digitValue() + ((hash << 5) - hash);
- }
+ for (int i = 0; i < input.length(); i++) {
+ hash = input.at(i).digitValue() + ((hash << 5) - hash);
+ }
- return hash;
+ return hash;
}
QColor
utils::generateContrastingHexColor(const QString &input, const QColor &backgroundCol)
{
- const qreal backgroundLum = luminance(backgroundCol);
-
- // Create a color for the input
- auto hash = hashQString(input);
- // create a hue value based on the hash of the input.
- auto userHue = static_cast<int>(hash % 360);
- // start with moderate saturation and lightness values.
- auto sat = 220;
- auto lightness = 125;
-
- // converting to a QColor makes the luminance calc easier.
- QColor inputColor = QColor::fromHsl(userHue, sat, lightness);
-
- // calculate the initial luminance and contrast of the
- // generated color. It's possible that no additional
- // work will be necessary.
- auto lum = luminance(inputColor);
- auto contrast = computeContrast(lum, backgroundLum);
-
- // If the contrast doesn't meet our criteria,
- // try again and again until they do by modifying first
- // the lightness and then the saturation of the color.
- int iterationCount = 9;
- while (contrast < 5) {
- // if our lightness is at it's bounds, try changing
- // saturation instead.
- if (lightness == 242 || lightness == 13) {
- qreal newSat = qBound(26.0, sat * 1.25, 242.0);
-
- inputColor.setHsl(userHue, qFloor(newSat), lightness);
- auto tmpLum = luminance(inputColor);
- auto higherContrast = computeContrast(tmpLum, backgroundLum);
- if (higherContrast > contrast) {
- contrast = higherContrast;
- sat = newSat;
- } else {
- newSat = qBound(26.0, sat / 1.25, 242.0);
- inputColor.setHsl(userHue, qFloor(newSat), lightness);
- tmpLum = luminance(inputColor);
- auto lowerContrast = computeContrast(tmpLum, backgroundLum);
- if (lowerContrast > contrast) {
- contrast = lowerContrast;
- sat = newSat;
- }
- }
- } else {
- qreal newLightness = qBound(13.0, lightness * 1.25, 242.0);
-
- inputColor.setHsl(userHue, sat, qFloor(newLightness));
-
- auto tmpLum = luminance(inputColor);
- auto higherContrast = computeContrast(tmpLum, backgroundLum);
-
- // Check to make sure we have actually improved contrast
- if (higherContrast > contrast) {
- contrast = higherContrast;
- lightness = newLightness;
- // otherwise, try going the other way instead.
- } else {
- newLightness = qBound(13.0, lightness / 1.25, 242.0);
- inputColor.setHsl(userHue, sat, qFloor(newLightness));
- tmpLum = luminance(inputColor);
- auto lowerContrast = computeContrast(tmpLum, backgroundLum);
- if (lowerContrast > contrast) {
- contrast = lowerContrast;
- lightness = newLightness;
- }
- }
+ const qreal backgroundLum = luminance(backgroundCol);
+
+ // Create a color for the input
+ auto hash = hashQString(input);
+ // create a hue value based on the hash of the input.
+ auto userHue = static_cast<int>(hash % 360);
+ // start with moderate saturation and lightness values.
+ auto sat = 220;
+ auto lightness = 125;
+
+ // converting to a QColor makes the luminance calc easier.
+ QColor inputColor = QColor::fromHsl(userHue, sat, lightness);
+
+ // calculate the initial luminance and contrast of the
+ // generated color. It's possible that no additional
+ // work will be necessary.
+ auto lum = luminance(inputColor);
+ auto contrast = computeContrast(lum, backgroundLum);
+
+ // If the contrast doesn't meet our criteria,
+ // try again and again until they do by modifying first
+ // the lightness and then the saturation of the color.
+ int iterationCount = 9;
+ while (contrast < 5) {
+ // if our lightness is at it's bounds, try changing
+ // saturation instead.
+ if (lightness == 242 || lightness == 13) {
+ qreal newSat = qBound(26.0, sat * 1.25, 242.0);
+
+ inputColor.setHsl(userHue, qFloor(newSat), lightness);
+ auto tmpLum = luminance(inputColor);
+ auto higherContrast = computeContrast(tmpLum, backgroundLum);
+ if (higherContrast > contrast) {
+ contrast = higherContrast;
+ sat = newSat;
+ } else {
+ newSat = qBound(26.0, sat / 1.25, 242.0);
+ inputColor.setHsl(userHue, qFloor(newSat), lightness);
+ tmpLum = luminance(inputColor);
+ auto lowerContrast = computeContrast(tmpLum, backgroundLum);
+ if (lowerContrast > contrast) {
+ contrast = lowerContrast;
+ sat = newSat;
}
-
- // don't loop forever, just give up at some point!
- // Someone smart may find a better solution
- if (--iterationCount < 0)
- break;
+ }
+ } else {
+ qreal newLightness = qBound(13.0, lightness * 1.25, 242.0);
+
+ inputColor.setHsl(userHue, sat, qFloor(newLightness));
+
+ auto tmpLum = luminance(inputColor);
+ auto higherContrast = computeContrast(tmpLum, backgroundLum);
+
+ // Check to make sure we have actually improved contrast
+ if (higherContrast > contrast) {
+ contrast = higherContrast;
+ lightness = newLightness;
+ // otherwise, try going the other way instead.
+ } else {
+ newLightness = qBound(13.0, lightness / 1.25, 242.0);
+ inputColor.setHsl(userHue, sat, qFloor(newLightness));
+ tmpLum = luminance(inputColor);
+ auto lowerContrast = computeContrast(tmpLum, backgroundLum);
+ if (lowerContrast > contrast) {
+ contrast = lowerContrast;
+ lightness = newLightness;
+ }
+ }
}
- // get the hex value of the generated color.
- auto colorHex = inputColor.name();
+ // don't loop forever, just give up at some point!
+ // Someone smart may find a better solution
+ if (--iterationCount < 0)
+ break;
+ }
- return colorHex;
+ // get the hex value of the generated color.
+ auto colorHex = inputColor.name();
+
+ return colorHex;
}
qreal
utils::computeContrast(const qreal &one, const qreal &two)
{
- auto ratio = (one + 0.05) / (two + 0.05);
+ auto ratio = (one + 0.05) / (two + 0.05);
- if (two > one) {
- ratio = 1 / ratio;
- }
+ if (two > one) {
+ ratio = 1 / ratio;
+ }
- return ratio;
+ return ratio;
}
qreal
utils::luminance(const QColor &col)
{
- int colRgb[3] = {col.red(), col.green(), col.blue()};
- qreal lumRgb[3];
+ int colRgb[3] = {col.red(), col.green(), col.blue()};
+ qreal lumRgb[3];
- for (int i = 0; i < 3; i++) {
- qreal v = colRgb[i] / 255.0;
- lumRgb[i] = v <= 0.03928 ? v / 12.92 : qPow((v + 0.055) / 1.055, 2.4);
- }
+ for (int i = 0; i < 3; i++) {
+ qreal v = colRgb[i] / 255.0;
+ lumRgb[i] = v <= 0.03928 ? v / 12.92 : qPow((v + 0.055) / 1.055, 2.4);
+ }
- auto lum = lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722;
+ auto lum = lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722;
- return lum;
+ return lum;
}
void
utils::centerWidget(QWidget *widget, QWidget *parent)
{
- auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint {
- return QPoint(hostRect.center().x() - (childRect.width() * 0.5),
- hostRect.center().y() - (childRect.height() * 0.5));
- };
+ auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint {
+ return QPoint(hostRect.center().x() - (childRect.width() * 0.5),
+ hostRect.center().y() - (childRect.height() * 0.5));
+ };
- if (parent) {
- widget->move(parent->window()->frameGeometry().topLeft() +
- parent->window()->rect().center() - widget->rect().center());
- return;
- }
+ if (parent) {
+ widget->move(parent->window()->frameGeometry().topLeft() +
+ parent->window()->rect().center() - widget->rect().center());
+ return;
+ }
- // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry()));
- widget->move(findCenter(QGuiApplication::primaryScreen()->geometry()));
+ // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry()));
+ widget->move(findCenter(QGuiApplication::primaryScreen()->geometry()));
}
void
utils::restoreCombobox(QComboBox *combo, const QString &value)
{
- for (auto i = 0; i < combo->count(); ++i) {
- if (value == combo->itemText(i)) {
- combo->setCurrentIndex(i);
- break;
- }
+ for (auto i = 0; i < combo->count(); ++i) {
+ if (value == combo->itemText(i)) {
+ combo->setCurrentIndex(i);
+ break;
}
+ }
}
QImage
utils::readImageFromFile(const QString &filename)
{
- QImageReader reader(filename);
- reader.setAutoTransform(true);
- return reader.read();
+ QImageReader reader(filename);
+ reader.setAutoTransform(true);
+ return reader.read();
}
QImage
utils::readImage(const QByteArray &data)
{
- QBuffer buf;
- buf.setData(data);
- QImageReader reader(&buf);
- reader.setAutoTransform(true);
- return reader.read();
+ QBuffer buf;
+ buf.setData(data);
+ QImageReader reader(&buf);
+ reader.setAutoTransform(true);
+ return reader.read();
}
bool
utils::isReply(const mtx::events::collections::TimelineEvents &e)
{
- return mtx::accessors::relations(e).reply_to().has_value();
+ return mtx::accessors::relations(e).reply_to().has_value();
}
diff --git a/src/Utils.h b/src/Utils.h
index 8f37a574..da82ec7c 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -29,12 +29,12 @@ class QComboBox;
// outgoing messages
struct RelatedInfo
{
- using MsgType = mtx::events::MessageType;
- MsgType type;
- QString room;
- QString quoted_body, quoted_formatted_body;
- std::string related_event;
- QString quoted_user;
+ using MsgType = mtx::events::MessageType;
+ MsgType type;
+ QString room;
+ QString quoted_body, quoted_formatted_body;
+ std::string related_event;
+ QString quoted_user;
};
namespace utils {
@@ -97,112 +97,96 @@ messageDescription(const QString &username = "",
const QString &body = "",
const bool isLocal = false)
{
- using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
- using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
- using File = mtx::events::RoomEvent<mtx::events::msg::File>;
- using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
- using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
- using Sticker = mtx::events::Sticker;
- using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
- using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
- using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
- using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
- using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
- using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
-
- if (std::is_same<T, Audio>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent an audio clip");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent an audio clip")
- .arg(username);
- } else if (std::is_same<T, Image>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent an image");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent an image")
- .arg(username);
- } else if (std::is_same<T, File>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent a file");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent a file")
- .arg(username);
- } else if (std::is_same<T, Video>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent a video");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent a video")
- .arg(username);
- } else if (std::is_same<T, Sticker>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent a sticker");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent a sticker")
- .arg(username);
- } else if (std::is_same<T, Notice>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent a notification");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent a notification")
- .arg(username);
- } else if (std::is_same<T, Text>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:", "You: %1")
- .arg(body);
- else
- return QCoreApplication::translate("message-description sent:", "%1: %2")
- .arg(username)
- .arg(body);
- } else if (std::is_same<T, Emote>::value) {
- return QString("* %1 %2").arg(username).arg(body);
- } else if (std::is_same<T, Encrypted>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You sent an encrypted message");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 sent an encrypted message")
- .arg(username);
- } else if (std::is_same<T, CallInvite>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You placed a call");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 placed a call")
- .arg(username);
- } else if (std::is_same<T, CallAnswer>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You answered a call");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 answered a call")
- .arg(username);
- } else if (std::is_same<T, CallHangUp>::value) {
- if (isLocal)
- return QCoreApplication::translate("message-description sent:",
- "You ended a call");
- else
- return QCoreApplication::translate("message-description sent:",
- "%1 ended a call")
- .arg(username);
- } else {
- return QCoreApplication::translate("utils", "Unknown Message Type");
- }
+ using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
+ using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
+ using File = mtx::events::RoomEvent<mtx::events::msg::File>;
+ using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
+ using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
+ using Sticker = mtx::events::Sticker;
+ using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
+ using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
+ using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
+ using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
+ using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
+ using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
+
+ if (std::is_same<T, Audio>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:",
+ "You sent an audio clip");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 sent an audio clip")
+ .arg(username);
+ } else if (std::is_same<T, Image>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You sent an image");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 sent an image")
+ .arg(username);
+ } else if (std::is_same<T, File>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You sent a file");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 sent a file")
+ .arg(username);
+ } else if (std::is_same<T, Video>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You sent a video");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 sent a video")
+ .arg(username);
+ } else if (std::is_same<T, Sticker>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You sent a sticker");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 sent a sticker")
+ .arg(username);
+ } else if (std::is_same<T, Notice>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:",
+ "You sent a notification");
+ else
+ return QCoreApplication::translate("message-description sent:",
+ "%1 sent a notification")
+ .arg(username);
+ } else if (std::is_same<T, Text>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You: %1").arg(body);
+ else
+ return QCoreApplication::translate("message-description sent:", "%1: %2")
+ .arg(username)
+ .arg(body);
+ } else if (std::is_same<T, Emote>::value) {
+ return QString("* %1 %2").arg(username).arg(body);
+ } else if (std::is_same<T, Encrypted>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:",
+ "You sent an encrypted message");
+ else
+ return QCoreApplication::translate("message-description sent:",
+ "%1 sent an encrypted message")
+ .arg(username);
+ } else if (std::is_same<T, CallInvite>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You placed a call");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 placed a call")
+ .arg(username);
+ } else if (std::is_same<T, CallAnswer>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You answered a call");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 answered a call")
+ .arg(username);
+ } else if (std::is_same<T, CallHangUp>::value) {
+ if (isLocal)
+ return QCoreApplication::translate("message-description sent:", "You ended a call");
+ else
+ return QCoreApplication::translate("message-description sent:", "%1 ended a call")
+ .arg(username);
+ } else {
+ return QCoreApplication::translate("utils", "Unknown Message Type");
+ }
}
//! Scale down an image to fit to the given width & height limitations.
@@ -214,19 +198,19 @@ template<typename ContainerT, typename PredicateT>
void
erase_if(ContainerT &items, const PredicateT &predicate)
{
- for (auto it = items.begin(); it != items.end();) {
- if (predicate(*it))
- it = items.erase(it);
- else
- ++it;
- }
+ for (auto it = items.begin(); it != items.end();) {
+ if (predicate(*it))
+ it = items.erase(it);
+ else
+ ++it;
+ }
}
template<class T>
QString
message_body(const mtx::events::collections::TimelineEvents &event)
{
- return QString::fromStdString(std::get<T>(event).content.body);
+ return QString::fromStdString(std::get<T>(event).content.body);
}
//! Calculate the Levenshtein distance between two strings with character skipping.
@@ -253,13 +237,13 @@ template<typename RoomMessageT>
QString
getMessageBody(const RoomMessageT &event)
{
- if (event.content.format.empty())
- return QString::fromStdString(event.content.body).toHtmlEscaped();
+ if (event.content.format.empty())
+ return QString::fromStdString(event.content.body).toHtmlEscaped();
- if (event.content.format != mtx::common::FORMAT_MSG_TYPE)
- return QString::fromStdString(event.content.body).toHtmlEscaped();
+ if (event.content.format != mtx::common::FORMAT_MSG_TYPE)
+ return QString::fromStdString(event.content.body).toHtmlEscaped();
- return QString::fromStdString(event.content.formatted_body);
+ return QString::fromStdString(event.content.formatted_body);
}
//! Replace raw URLs in text with HTML link tags.
diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp
index 72330435..801a365c 100644
--- a/src/WebRTCSession.cpp
+++ b/src/WebRTCSession.cpp
@@ -43,47 +43,47 @@ using webrtc::State;
WebRTCSession::WebRTCSession()
: devices_(CallDevices::instance())
{
- qRegisterMetaType<webrtc::CallType>();
- qmlRegisterUncreatableMetaObject(
- webrtc::staticMetaObject, "im.nheko", 1, 0, "CallType", "Can't instantiate enum");
+ qRegisterMetaType<webrtc::CallType>();
+ qmlRegisterUncreatableMetaObject(
+ webrtc::staticMetaObject, "im.nheko", 1, 0, "CallType", "Can't instantiate enum");
- qRegisterMetaType<webrtc::State>();
- qmlRegisterUncreatableMetaObject(
- webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum");
+ qRegisterMetaType<webrtc::State>();
+ qmlRegisterUncreatableMetaObject(
+ webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum");
- connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
- init();
+ connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
+ init();
}
bool
WebRTCSession::init(std::string *errorMessage)
{
#ifdef GSTREAMER_AVAILABLE
- if (initialised_)
- return true;
+ if (initialised_)
+ return true;
- GError *error = nullptr;
- if (!gst_init_check(nullptr, nullptr, &error)) {
- std::string strError("WebRTC: failed to initialise GStreamer: ");
- if (error) {
- strError += error->message;
- g_error_free(error);
- }
- nhlog::ui()->error(strError);
- if (errorMessage)
- *errorMessage = strError;
- return false;
+ GError *error = nullptr;
+ if (!gst_init_check(nullptr, nullptr, &error)) {
+ std::string strError("WebRTC: failed to initialise GStreamer: ");
+ if (error) {
+ strError += error->message;
+ g_error_free(error);
}
-
- initialised_ = true;
- gchar *version = gst_version_string();
- nhlog::ui()->info("WebRTC: initialised {}", version);
- g_free(version);
- devices_.init();
- return true;
-#else
- (void)errorMessage;
+ nhlog::ui()->error(strError);
+ if (errorMessage)
+ *errorMessage = strError;
return false;
+ }
+
+ initialised_ = true;
+ gchar *version = gst_version_string();
+ nhlog::ui()->info("WebRTC: initialised {}", version);
+ g_free(version);
+ devices_.init();
+ return true;
+#else
+ (void)errorMessage;
+ return false;
#endif
}
@@ -100,81 +100,77 @@ GstPad *remotePiPSinkPad_ = nullptr;
gboolean
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data)
{
- WebRTCSession *session = static_cast<WebRTCSession *>(user_data);
- switch (GST_MESSAGE_TYPE(msg)) {
- case GST_MESSAGE_EOS:
- nhlog::ui()->error("WebRTC: end of stream");
- session->end();
- break;
- case GST_MESSAGE_ERROR:
- GError *error;
- gchar *debug;
- gst_message_parse_error(msg, &error, &debug);
- nhlog::ui()->error(
- "WebRTC: error from element {}: {}", GST_OBJECT_NAME(msg->src), error->message);
- g_clear_error(&error);
- g_free(debug);
- session->end();
- break;
- default:
- break;
- }
- return TRUE;
+ WebRTCSession *session = static_cast<WebRTCSession *>(user_data);
+ switch (GST_MESSAGE_TYPE(msg)) {
+ case GST_MESSAGE_EOS:
+ nhlog::ui()->error("WebRTC: end of stream");
+ session->end();
+ break;
+ case GST_MESSAGE_ERROR:
+ GError *error;
+ gchar *debug;
+ gst_message_parse_error(msg, &error, &debug);
+ nhlog::ui()->error(
+ "WebRTC: error from element {}: {}", GST_OBJECT_NAME(msg->src), error->message);
+ g_clear_error(&error);
+ g_free(debug);
+ session->end();
+ break;
+ default:
+ break;
+ }
+ return TRUE;
}
GstWebRTCSessionDescription *
parseSDP(const std::string &sdp, GstWebRTCSDPType type)
{
- GstSDPMessage *msg;
- gst_sdp_message_new(&msg);
- if (gst_sdp_message_parse_buffer((guint8 *)sdp.c_str(), sdp.size(), msg) == GST_SDP_OK) {
- return gst_webrtc_session_description_new(type, msg);
- } else {
- nhlog::ui()->error("WebRTC: failed to parse remote session description");
- gst_sdp_message_free(msg);
- return nullptr;
- }
+ GstSDPMessage *msg;
+ gst_sdp_message_new(&msg);
+ if (gst_sdp_message_parse_buffer((guint8 *)sdp.c_str(), sdp.size(), msg) == GST_SDP_OK) {
+ return gst_webrtc_session_description_new(type, msg);
+ } else {
+ nhlog::ui()->error("WebRTC: failed to parse remote session description");
+ gst_sdp_message_free(msg);
+ return nullptr;
+ }
}
void
setLocalDescription(GstPromise *promise, gpointer webrtc)
{
- const GstStructure *reply = gst_promise_get_reply(promise);
- gboolean isAnswer = gst_structure_id_has_field(reply, g_quark_from_string("answer"));
- GstWebRTCSessionDescription *gstsdp = nullptr;
- gst_structure_get(reply,
- isAnswer ? "answer" : "offer",
- GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
- &gstsdp,
- nullptr);
- gst_promise_unref(promise);
- g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr);
-
- gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp);
- localsdp_ = std::string(sdp);
- g_free(sdp);
- gst_webrtc_session_description_free(gstsdp);
-
- nhlog::ui()->debug(
- "WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_);
+ const GstStructure *reply = gst_promise_get_reply(promise);
+ gboolean isAnswer = gst_structure_id_has_field(reply, g_quark_from_string("answer"));
+ GstWebRTCSessionDescription *gstsdp = nullptr;
+ gst_structure_get(
+ reply, isAnswer ? "answer" : "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &gstsdp, nullptr);
+ gst_promise_unref(promise);
+ g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr);
+
+ gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp);
+ localsdp_ = std::string(sdp);
+ g_free(sdp);
+ gst_webrtc_session_description_free(gstsdp);
+
+ nhlog::ui()->debug(
+ "WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_);
}
void
createOffer(GstElement *webrtc)
{
- // create-offer first, then set-local-description
- GstPromise *promise =
- gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
- g_signal_emit_by_name(webrtc, "create-offer", nullptr, promise);
+ // create-offer first, then set-local-description
+ GstPromise *promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
+ g_signal_emit_by_name(webrtc, "create-offer", nullptr, promise);
}
void
createAnswer(GstPromise *promise, gpointer webrtc)
{
- // create-answer first, then set-local-description
- gst_promise_unref(promise);
- promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
- g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise);
+ // create-answer first, then set-local-description
+ gst_promise_unref(promise);
+ promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
+ g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise);
}
void
@@ -182,18 +178,18 @@ iceGatheringStateChanged(GstElement *webrtc,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data G_GNUC_UNUSED)
{
- GstWebRTCICEGatheringState newState;
- g_object_get(webrtc, "ice-gathering-state", &newState, nullptr);
- if (newState == GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) {
- nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
- if (WebRTCSession::instance().isOffering()) {
- emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
- } else {
- emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
- }
+ GstWebRTCICEGatheringState newState;
+ g_object_get(webrtc, "ice-gathering-state", &newState, nullptr);
+ if (newState == GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) {
+ nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
+ if (WebRTCSession::instance().isOffering()) {
+ emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
+ emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
+ } else {
+ emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
+ emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
}
+ }
}
void
@@ -202,8 +198,8 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
gchar *candidate,
gpointer G_GNUC_UNUSED)
{
- nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate);
- localcandidates_.push_back({std::string() /*max-bundle*/, (uint16_t)mlineIndex, candidate});
+ nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate);
+ localcandidates_.push_back({std::string() /*max-bundle*/, (uint16_t)mlineIndex, candidate});
}
void
@@ -211,337 +207,327 @@ iceConnectionStateChanged(GstElement *webrtc,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data G_GNUC_UNUSED)
{
- GstWebRTCICEConnectionState newState;
- g_object_get(webrtc, "ice-connection-state", &newState, nullptr);
- switch (newState) {
- case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
- nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
- emit WebRTCSession::instance().stateChanged(State::CONNECTING);
- break;
- case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
- nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
- emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
- break;
- default:
- break;
- }
+ GstWebRTCICEConnectionState newState;
+ g_object_get(webrtc, "ice-connection-state", &newState, nullptr);
+ switch (newState) {
+ case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
+ nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
+ emit WebRTCSession::instance().stateChanged(State::CONNECTING);
+ break;
+ case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
+ nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
+ emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
+ break;
+ default:
+ break;
+ }
}
// https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1164
struct KeyFrameRequestData
{
- GstElement *pipe = nullptr;
- GstElement *decodebin = nullptr;
- gint packetsLost = 0;
- guint timerid = 0;
- std::string statsField;
+ GstElement *pipe = nullptr;
+ GstElement *decodebin = nullptr;
+ gint packetsLost = 0;
+ guint timerid = 0;
+ std::string statsField;
} keyFrameRequestData_;
void
sendKeyFrameRequest()
{
- GstPad *sinkpad = gst_element_get_static_pad(keyFrameRequestData_.decodebin, "sink");
- if (!gst_pad_push_event(sinkpad,
- gst_event_new_custom(GST_EVENT_CUSTOM_UPSTREAM,
- gst_structure_new_empty("GstForceKeyUnit"))))
- nhlog::ui()->error("WebRTC: key frame request failed");
- else
- nhlog::ui()->debug("WebRTC: sent key frame request");
-
- gst_object_unref(sinkpad);
+ GstPad *sinkpad = gst_element_get_static_pad(keyFrameRequestData_.decodebin, "sink");
+ if (!gst_pad_push_event(sinkpad,
+ gst_event_new_custom(GST_EVENT_CUSTOM_UPSTREAM,
+ gst_structure_new_empty("GstForceKeyUnit"))))
+ nhlog::ui()->error("WebRTC: key frame request failed");
+ else
+ nhlog::ui()->debug("WebRTC: sent key frame request");
+
+ gst_object_unref(sinkpad);
}
void
testPacketLoss_(GstPromise *promise, gpointer G_GNUC_UNUSED)
{
- const GstStructure *reply = gst_promise_get_reply(promise);
- gint packetsLost = 0;
- GstStructure *rtpStats;
- if (!gst_structure_get(reply,
- keyFrameRequestData_.statsField.c_str(),
- GST_TYPE_STRUCTURE,
- &rtpStats,
- nullptr)) {
- nhlog::ui()->error("WebRTC: get-stats: no field: {}",
- keyFrameRequestData_.statsField);
- gst_promise_unref(promise);
- return;
- }
- gst_structure_get_int(rtpStats, "packets-lost", &packetsLost);
- gst_structure_free(rtpStats);
+ const GstStructure *reply = gst_promise_get_reply(promise);
+ gint packetsLost = 0;
+ GstStructure *rtpStats;
+ if (!gst_structure_get(
+ reply, keyFrameRequestData_.statsField.c_str(), GST_TYPE_STRUCTURE, &rtpStats, nullptr)) {
+ nhlog::ui()->error("WebRTC: get-stats: no field: {}", keyFrameRequestData_.statsField);
gst_promise_unref(promise);
- if (packetsLost > keyFrameRequestData_.packetsLost) {
- nhlog::ui()->debug("WebRTC: inbound video lost packet count: {}", packetsLost);
- keyFrameRequestData_.packetsLost = packetsLost;
- sendKeyFrameRequest();
- }
+ return;
+ }
+ gst_structure_get_int(rtpStats, "packets-lost", &packetsLost);
+ gst_structure_free(rtpStats);
+ gst_promise_unref(promise);
+ if (packetsLost > keyFrameRequestData_.packetsLost) {
+ nhlog::ui()->debug("WebRTC: inbound video lost packet count: {}", packetsLost);
+ keyFrameRequestData_.packetsLost = packetsLost;
+ sendKeyFrameRequest();
+ }
}
gboolean
testPacketLoss(gpointer G_GNUC_UNUSED)
{
- if (keyFrameRequestData_.pipe) {
- GstElement *webrtc =
- gst_bin_get_by_name(GST_BIN(keyFrameRequestData_.pipe), "webrtcbin");
- GstPromise *promise =
- gst_promise_new_with_change_func(testPacketLoss_, nullptr, nullptr);
- g_signal_emit_by_name(webrtc, "get-stats", nullptr, promise);
- gst_object_unref(webrtc);
- return TRUE;
- }
- return FALSE;
+ if (keyFrameRequestData_.pipe) {
+ GstElement *webrtc = gst_bin_get_by_name(GST_BIN(keyFrameRequestData_.pipe), "webrtcbin");
+ GstPromise *promise = gst_promise_new_with_change_func(testPacketLoss_, nullptr, nullptr);
+ g_signal_emit_by_name(webrtc, "get-stats", nullptr, promise);
+ gst_object_unref(webrtc);
+ return TRUE;
+ }
+ return FALSE;
}
void
setWaitForKeyFrame(GstBin *decodebin G_GNUC_UNUSED, GstElement *element, gpointer G_GNUC_UNUSED)
{
- if (!std::strcmp(
- gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(gst_element_get_factory(element))),
- "rtpvp8depay"))
- g_object_set(element, "wait-for-keyframe", TRUE, nullptr);
+ if (!std::strcmp(
+ gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(gst_element_get_factory(element))),
+ "rtpvp8depay"))
+ g_object_set(element, "wait-for-keyframe", TRUE, nullptr);
}
GstElement *
newAudioSinkChain(GstElement *pipe)
{
- GstElement *queue = gst_element_factory_make("queue", nullptr);
- GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
- GstElement *resample = gst_element_factory_make("audioresample", nullptr);
- GstElement *sink = gst_element_factory_make("autoaudiosink", nullptr);
- gst_bin_add_many(GST_BIN(pipe), queue, convert, resample, sink, nullptr);
- gst_element_link_many(queue, convert, resample, sink, nullptr);
- gst_element_sync_state_with_parent(queue);
- gst_element_sync_state_with_parent(convert);
- gst_element_sync_state_with_parent(resample);
- gst_element_sync_state_with_parent(sink);
- return queue;
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
+ GstElement *resample = gst_element_factory_make("audioresample", nullptr);
+ GstElement *sink = gst_element_factory_make("autoaudiosink", nullptr);
+ gst_bin_add_many(GST_BIN(pipe), queue, convert, resample, sink, nullptr);
+ gst_element_link_many(queue, convert, resample, sink, nullptr);
+ gst_element_sync_state_with_parent(queue);
+ gst_element_sync_state_with_parent(convert);
+ gst_element_sync_state_with_parent(resample);
+ gst_element_sync_state_with_parent(sink);
+ return queue;
}
GstElement *
newVideoSinkChain(GstElement *pipe)
{
- // use compositor for now; acceleration needs investigation
- GstElement *queue = gst_element_factory_make("queue", nullptr);
- GstElement *compositor = gst_element_factory_make("compositor", "compositor");
- GstElement *glupload = gst_element_factory_make("glupload", nullptr);
- GstElement *glcolorconvert = gst_element_factory_make("glcolorconvert", nullptr);
- GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr);
- GstElement *glsinkbin = gst_element_factory_make("glsinkbin", nullptr);
- g_object_set(compositor, "background", 1, nullptr);
- g_object_set(qmlglsink, "widget", WebRTCSession::instance().getVideoItem(), nullptr);
- g_object_set(glsinkbin, "sink", qmlglsink, nullptr);
- gst_bin_add_many(
- GST_BIN(pipe), queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
- gst_element_link_many(queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
- gst_element_sync_state_with_parent(queue);
- gst_element_sync_state_with_parent(compositor);
- gst_element_sync_state_with_parent(glupload);
- gst_element_sync_state_with_parent(glcolorconvert);
- gst_element_sync_state_with_parent(glsinkbin);
- return queue;
+ // use compositor for now; acceleration needs investigation
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ GstElement *compositor = gst_element_factory_make("compositor", "compositor");
+ GstElement *glupload = gst_element_factory_make("glupload", nullptr);
+ GstElement *glcolorconvert = gst_element_factory_make("glcolorconvert", nullptr);
+ GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr);
+ GstElement *glsinkbin = gst_element_factory_make("glsinkbin", nullptr);
+ g_object_set(compositor, "background", 1, nullptr);
+ g_object_set(qmlglsink, "widget", WebRTCSession::instance().getVideoItem(), nullptr);
+ g_object_set(glsinkbin, "sink", qmlglsink, nullptr);
+ gst_bin_add_many(
+ GST_BIN(pipe), queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
+ gst_element_link_many(queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
+ gst_element_sync_state_with_parent(queue);
+ gst_element_sync_state_with_parent(compositor);
+ gst_element_sync_state_with_parent(glupload);
+ gst_element_sync_state_with_parent(glcolorconvert);
+ gst_element_sync_state_with_parent(glsinkbin);
+ return queue;
}
std::pair<int, int>
getResolution(GstPad *pad)
{
- std::pair<int, int> ret;
- GstCaps *caps = gst_pad_get_current_caps(pad);
- const GstStructure *s = gst_caps_get_structure(caps, 0);
- gst_structure_get_int(s, "width", &ret.first);
- gst_structure_get_int(s, "height", &ret.second);
- gst_caps_unref(caps);
- return ret;
+ std::pair<int, int> ret;
+ GstCaps *caps = gst_pad_get_current_caps(pad);
+ const GstStructure *s = gst_caps_get_structure(caps, 0);
+ gst_structure_get_int(s, "width", &ret.first);
+ gst_structure_get_int(s, "height", &ret.second);
+ gst_caps_unref(caps);
+ return ret;
}
std::pair<int, int>
getResolution(GstElement *pipe, const gchar *elementName, const gchar *padName)
{
- GstElement *element = gst_bin_get_by_name(GST_BIN(pipe), elementName);
- GstPad *pad = gst_element_get_static_pad(element, padName);
- auto ret = getResolution(pad);
- gst_object_unref(pad);
- gst_object_unref(element);
- return ret;
+ GstElement *element = gst_bin_get_by_name(GST_BIN(pipe), elementName);
+ GstPad *pad = gst_element_get_static_pad(element, padName);
+ auto ret = getResolution(pad);
+ gst_object_unref(pad);
+ gst_object_unref(element);
+ return ret;
}
std::pair<int, int>
getPiPDimensions(const std::pair<int, int> &resolution, int fullWidth, double scaleFactor)
{
- int pipWidth = fullWidth * scaleFactor;
- int pipHeight = static_cast<double>(resolution.second) / resolution.first * pipWidth;
- return {pipWidth, pipHeight};
+ int pipWidth = fullWidth * scaleFactor;
+ int pipHeight = static_cast<double>(resolution.second) / resolution.first * pipWidth;
+ return {pipWidth, pipHeight};
}
void
addLocalPiP(GstElement *pipe, const std::pair<int, int> &videoCallSize)
{
- // embed localUser's camera into received video (CallType::VIDEO)
- // OR embed screen share into received video (CallType::SCREEN)
- GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
- if (!tee)
- return;
-
- GstElement *queue = gst_element_factory_make("queue", nullptr);
- gst_bin_add(GST_BIN(pipe), queue);
- gst_element_link(tee, queue);
- gst_element_sync_state_with_parent(queue);
- gst_object_unref(tee);
-
- GstElement *compositor = gst_bin_get_by_name(GST_BIN(pipe), "compositor");
- localPiPSinkPad_ = gst_element_get_request_pad(compositor, "sink_%u");
- g_object_set(localPiPSinkPad_, "zorder", 2, nullptr);
-
- bool isVideo = WebRTCSession::instance().callType() == CallType::VIDEO;
- const gchar *element = isVideo ? "camerafilter" : "screenshare";
- const gchar *pad = isVideo ? "sink" : "src";
- auto resolution = getResolution(pipe, element, pad);
- auto pipSize = getPiPDimensions(resolution, videoCallSize.first, 0.25);
- nhlog::ui()->debug(
- "WebRTC: local picture-in-picture: {}x{}", pipSize.first, pipSize.second);
- g_object_set(localPiPSinkPad_, "width", pipSize.first, "height", pipSize.second, nullptr);
- gint offset = videoCallSize.first / 80;
- g_object_set(localPiPSinkPad_, "xpos", offset, "ypos", offset, nullptr);
-
- GstPad *srcpad = gst_element_get_static_pad(queue, "src");
- if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, localPiPSinkPad_)))
- nhlog::ui()->error("WebRTC: failed to link local PiP elements");
- gst_object_unref(srcpad);
- gst_object_unref(compositor);
+ // embed localUser's camera into received video (CallType::VIDEO)
+ // OR embed screen share into received video (CallType::SCREEN)
+ GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
+ if (!tee)
+ return;
+
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ gst_bin_add(GST_BIN(pipe), queue);
+ gst_element_link(tee, queue);
+ gst_element_sync_state_with_parent(queue);
+ gst_object_unref(tee);
+
+ GstElement *compositor = gst_bin_get_by_name(GST_BIN(pipe), "compositor");
+ localPiPSinkPad_ = gst_element_get_request_pad(compositor, "sink_%u");
+ g_object_set(localPiPSinkPad_, "zorder", 2, nullptr);
+
+ bool isVideo = WebRTCSession::instance().callType() == CallType::VIDEO;
+ const gchar *element = isVideo ? "camerafilter" : "screenshare";
+ const gchar *pad = isVideo ? "sink" : "src";
+ auto resolution = getResolution(pipe, element, pad);
+ auto pipSize = getPiPDimensions(resolution, videoCallSize.first, 0.25);
+ nhlog::ui()->debug("WebRTC: local picture-in-picture: {}x{}", pipSize.first, pipSize.second);
+ g_object_set(localPiPSinkPad_, "width", pipSize.first, "height", pipSize.second, nullptr);
+ gint offset = videoCallSize.first / 80;
+ g_object_set(localPiPSinkPad_, "xpos", offset, "ypos", offset, nullptr);
+
+ GstPad *srcpad = gst_element_get_static_pad(queue, "src");
+ if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, localPiPSinkPad_)))
+ nhlog::ui()->error("WebRTC: failed to link local PiP elements");
+ gst_object_unref(srcpad);
+ gst_object_unref(compositor);
}
void
addRemotePiP(GstElement *pipe)
{
- // embed localUser's camera into screen image being shared
- if (remotePiPSinkPad_) {
- auto camRes = getResolution(pipe, "camerafilter", "sink");
- auto shareRes = getResolution(pipe, "screenshare", "src");
- auto pipSize = getPiPDimensions(camRes, shareRes.first, 0.2);
- nhlog::ui()->debug(
- "WebRTC: screen share picture-in-picture: {}x{}", pipSize.first, pipSize.second);
-
- gint offset = shareRes.first / 100;
- g_object_set(remotePiPSinkPad_, "zorder", 2, nullptr);
- g_object_set(
- remotePiPSinkPad_, "width", pipSize.first, "height", pipSize.second, nullptr);
- g_object_set(remotePiPSinkPad_,
- "xpos",
- shareRes.first - pipSize.first - offset,
- "ypos",
- shareRes.second - pipSize.second - offset,
- nullptr);
- }
+ // embed localUser's camera into screen image being shared
+ if (remotePiPSinkPad_) {
+ auto camRes = getResolution(pipe, "camerafilter", "sink");
+ auto shareRes = getResolution(pipe, "screenshare", "src");
+ auto pipSize = getPiPDimensions(camRes, shareRes.first, 0.2);
+ nhlog::ui()->debug(
+ "WebRTC: screen share picture-in-picture: {}x{}", pipSize.first, pipSize.second);
+
+ gint offset = shareRes.first / 100;
+ g_object_set(remotePiPSinkPad_, "zorder", 2, nullptr);
+ g_object_set(remotePiPSinkPad_, "width", pipSize.first, "height", pipSize.second, nullptr);
+ g_object_set(remotePiPSinkPad_,
+ "xpos",
+ shareRes.first - pipSize.first - offset,
+ "ypos",
+ shareRes.second - pipSize.second - offset,
+ nullptr);
+ }
}
void
addLocalVideo(GstElement *pipe)
{
- GstElement *queue = newVideoSinkChain(pipe);
- GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
- GstPad *srcpad = gst_element_get_request_pad(tee, "src_%u");
- GstPad *sinkpad = gst_element_get_static_pad(queue, "sink");
- if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, sinkpad)))
- nhlog::ui()->error("WebRTC: failed to link videosrctee -> video sink chain");
- gst_object_unref(srcpad);
+ GstElement *queue = newVideoSinkChain(pipe);
+ GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
+ GstPad *srcpad = gst_element_get_request_pad(tee, "src_%u");
+ GstPad *sinkpad = gst_element_get_static_pad(queue, "sink");
+ if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, sinkpad)))
+ nhlog::ui()->error("WebRTC: failed to link videosrctee -> video sink chain");
+ gst_object_unref(srcpad);
}
void
linkNewPad(GstElement *decodebin, GstPad *newpad, GstElement *pipe)
{
- GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
- GstCaps *sinkcaps = gst_pad_get_current_caps(sinkpad);
- const GstStructure *structure = gst_caps_get_structure(sinkcaps, 0);
-
- gchar *mediaType = nullptr;
- guint ssrc = 0;
- gst_structure_get(
- structure, "media", G_TYPE_STRING, &mediaType, "ssrc", G_TYPE_UINT, &ssrc, nullptr);
- gst_caps_unref(sinkcaps);
- gst_object_unref(sinkpad);
-
- WebRTCSession *session = &WebRTCSession::instance();
- GstElement *queue = nullptr;
- if (!std::strcmp(mediaType, "audio")) {
- nhlog::ui()->debug("WebRTC: received incoming audio stream");
- haveAudioStream_ = true;
- queue = newAudioSinkChain(pipe);
- } else if (!std::strcmp(mediaType, "video")) {
- nhlog::ui()->debug("WebRTC: received incoming video stream");
- if (!session->getVideoItem()) {
- g_free(mediaType);
- nhlog::ui()->error("WebRTC: video call item not set");
- return;
- }
- haveVideoStream_ = true;
- keyFrameRequestData_.statsField =
- std::string("rtp-inbound-stream-stats_") + std::to_string(ssrc);
- queue = newVideoSinkChain(pipe);
- auto videoCallSize = getResolution(newpad);
- nhlog::ui()->info("WebRTC: incoming video resolution: {}x{}",
- videoCallSize.first,
- videoCallSize.second);
- addLocalPiP(pipe, videoCallSize);
- } else {
- g_free(mediaType);
- nhlog::ui()->error("WebRTC: unknown pad type: {}", GST_PAD_NAME(newpad));
- return;
+ GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
+ GstCaps *sinkcaps = gst_pad_get_current_caps(sinkpad);
+ const GstStructure *structure = gst_caps_get_structure(sinkcaps, 0);
+
+ gchar *mediaType = nullptr;
+ guint ssrc = 0;
+ gst_structure_get(
+ structure, "media", G_TYPE_STRING, &mediaType, "ssrc", G_TYPE_UINT, &ssrc, nullptr);
+ gst_caps_unref(sinkcaps);
+ gst_object_unref(sinkpad);
+
+ WebRTCSession *session = &WebRTCSession::instance();
+ GstElement *queue = nullptr;
+ if (!std::strcmp(mediaType, "audio")) {
+ nhlog::ui()->debug("WebRTC: received incoming audio stream");
+ haveAudioStream_ = true;
+ queue = newAudioSinkChain(pipe);
+ } else if (!std::strcmp(mediaType, "video")) {
+ nhlog::ui()->debug("WebRTC: received incoming video stream");
+ if (!session->getVideoItem()) {
+ g_free(mediaType);
+ nhlog::ui()->error("WebRTC: video call item not set");
+ return;
}
-
- GstPad *queuepad = gst_element_get_static_pad(queue, "sink");
- if (queuepad) {
- if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
- nhlog::ui()->error("WebRTC: unable to link new pad");
- else {
- if (session->callType() == CallType::VOICE ||
- (haveAudioStream_ &&
- (haveVideoStream_ || session->isRemoteVideoRecvOnly()))) {
- emit session->stateChanged(State::CONNECTED);
- if (haveVideoStream_) {
- keyFrameRequestData_.pipe = pipe;
- keyFrameRequestData_.decodebin = decodebin;
- keyFrameRequestData_.timerid =
- g_timeout_add_seconds(3, testPacketLoss, nullptr);
- }
- addRemotePiP(pipe);
- if (session->isRemoteVideoRecvOnly())
- addLocalVideo(pipe);
- }
+ haveVideoStream_ = true;
+ keyFrameRequestData_.statsField =
+ std::string("rtp-inbound-stream-stats_") + std::to_string(ssrc);
+ queue = newVideoSinkChain(pipe);
+ auto videoCallSize = getResolution(newpad);
+ nhlog::ui()->info(
+ "WebRTC: incoming video resolution: {}x{}", videoCallSize.first, videoCallSize.second);
+ addLocalPiP(pipe, videoCallSize);
+ } else {
+ g_free(mediaType);
+ nhlog::ui()->error("WebRTC: unknown pad type: {}", GST_PAD_NAME(newpad));
+ return;
+ }
+
+ GstPad *queuepad = gst_element_get_static_pad(queue, "sink");
+ if (queuepad) {
+ if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
+ nhlog::ui()->error("WebRTC: unable to link new pad");
+ else {
+ if (session->callType() == CallType::VOICE ||
+ (haveAudioStream_ && (haveVideoStream_ || session->isRemoteVideoRecvOnly()))) {
+ emit session->stateChanged(State::CONNECTED);
+ if (haveVideoStream_) {
+ keyFrameRequestData_.pipe = pipe;
+ keyFrameRequestData_.decodebin = decodebin;
+ keyFrameRequestData_.timerid =
+ g_timeout_add_seconds(3, testPacketLoss, nullptr);
}
- gst_object_unref(queuepad);
+ addRemotePiP(pipe);
+ if (session->isRemoteVideoRecvOnly())
+ addLocalVideo(pipe);
+ }
}
- g_free(mediaType);
+ gst_object_unref(queuepad);
+ }
+ g_free(mediaType);
}
void
addDecodeBin(GstElement *webrtc G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
{
- if (GST_PAD_DIRECTION(newpad) != GST_PAD_SRC)
- return;
-
- nhlog::ui()->debug("WebRTC: received incoming stream");
- GstElement *decodebin = gst_element_factory_make("decodebin", nullptr);
- // hardware decoding needs investigation; eg rendering fails if vaapi plugin installed
- g_object_set(decodebin, "force-sw-decoders", TRUE, nullptr);
- g_signal_connect(decodebin, "pad-added", G_CALLBACK(linkNewPad), pipe);
- g_signal_connect(decodebin, "element-added", G_CALLBACK(setWaitForKeyFrame), nullptr);
- gst_bin_add(GST_BIN(pipe), decodebin);
- gst_element_sync_state_with_parent(decodebin);
- GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
- if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, sinkpad)))
- nhlog::ui()->error("WebRTC: unable to link decodebin");
- gst_object_unref(sinkpad);
+ if (GST_PAD_DIRECTION(newpad) != GST_PAD_SRC)
+ return;
+
+ nhlog::ui()->debug("WebRTC: received incoming stream");
+ GstElement *decodebin = gst_element_factory_make("decodebin", nullptr);
+ // hardware decoding needs investigation; eg rendering fails if vaapi plugin installed
+ g_object_set(decodebin, "force-sw-decoders", TRUE, nullptr);
+ g_signal_connect(decodebin, "pad-added", G_CALLBACK(linkNewPad), pipe);
+ g_signal_connect(decodebin, "element-added", G_CALLBACK(setWaitForKeyFrame), nullptr);
+ gst_bin_add(GST_BIN(pipe), decodebin);
+ gst_element_sync_state_with_parent(decodebin);
+ GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
+ if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, sinkpad)))
+ nhlog::ui()->error("WebRTC: unable to link decodebin");
+ gst_object_unref(sinkpad);
}
bool
contains(std::string_view str1, std::string_view str2)
{
- return std::search(str1.cbegin(),
- str1.cend(),
- str2.cbegin(),
- str2.cend(),
- [](unsigned char c1, unsigned char c2) {
- return std::tolower(c1) == std::tolower(c2);
- }) != str1.cend();
+ return std::search(str1.cbegin(),
+ str1.cend(),
+ str2.cbegin(),
+ str2.cend(),
+ [](unsigned char c1, unsigned char c2) {
+ return std::tolower(c1) == std::tolower(c2);
+ }) != str1.cend();
}
bool
@@ -552,582 +538,566 @@ getMediaAttributes(const GstSDPMessage *sdp,
bool &recvOnly,
bool &sendOnly)
{
- payloadType = -1;
- recvOnly = false;
- sendOnly = false;
- for (guint mlineIndex = 0; mlineIndex < gst_sdp_message_medias_len(sdp); ++mlineIndex) {
- const GstSDPMedia *media = gst_sdp_message_get_media(sdp, mlineIndex);
- if (!std::strcmp(gst_sdp_media_get_media(media), mediaType)) {
- recvOnly = gst_sdp_media_get_attribute_val(media, "recvonly") != nullptr;
- sendOnly = gst_sdp_media_get_attribute_val(media, "sendonly") != nullptr;
- const gchar *rtpval = nullptr;
- for (guint n = 0; n == 0 || rtpval; ++n) {
- rtpval = gst_sdp_media_get_attribute_val_n(media, "rtpmap", n);
- if (rtpval && contains(rtpval, encoding)) {
- payloadType = std::atoi(rtpval);
- break;
- }
- }
- return true;
+ payloadType = -1;
+ recvOnly = false;
+ sendOnly = false;
+ for (guint mlineIndex = 0; mlineIndex < gst_sdp_message_medias_len(sdp); ++mlineIndex) {
+ const GstSDPMedia *media = gst_sdp_message_get_media(sdp, mlineIndex);
+ if (!std::strcmp(gst_sdp_media_get_media(media), mediaType)) {
+ recvOnly = gst_sdp_media_get_attribute_val(media, "recvonly") != nullptr;
+ sendOnly = gst_sdp_media_get_attribute_val(media, "sendonly") != nullptr;
+ const gchar *rtpval = nullptr;
+ for (guint n = 0; n == 0 || rtpval; ++n) {
+ rtpval = gst_sdp_media_get_attribute_val_n(media, "rtpmap", n);
+ if (rtpval && contains(rtpval, encoding)) {
+ payloadType = std::atoi(rtpval);
+ break;
}
+ }
+ return true;
}
- return false;
+ }
+ return false;
}
}
bool
WebRTCSession::havePlugins(bool isVideo, std::string *errorMessage)
{
- if (!initialised_ && !init(errorMessage))
- return false;
- if (!isVideo && haveVoicePlugins_)
- return true;
- if (isVideo && haveVideoPlugins_)
- return true;
-
- const gchar *voicePlugins[] = {"audioconvert",
- "audioresample",
- "autodetect",
- "dtls",
- "nice",
- "opus",
- "playback",
- "rtpmanager",
- "srtp",
- "volume",
- "webrtc",
- nullptr};
-
- const gchar *videoPlugins[] = {
- "compositor", "opengl", "qmlgl", "rtp", "videoconvert", "vpx", nullptr};
-
- std::string strError("Missing GStreamer plugins: ");
- const gchar **needed = isVideo ? videoPlugins : voicePlugins;
- bool &havePlugins = isVideo ? haveVideoPlugins_ : haveVoicePlugins_;
- havePlugins = true;
- GstRegistry *registry = gst_registry_get();
- for (guint i = 0; i < g_strv_length((gchar **)needed); i++) {
- GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
- if (!plugin) {
- havePlugins = false;
- strError += std::string(needed[i]) + " ";
- continue;
- }
- gst_object_unref(plugin);
- }
- if (!havePlugins) {
- nhlog::ui()->error(strError);
- if (errorMessage)
- *errorMessage = strError;
- return false;
- }
+ if (!initialised_ && !init(errorMessage))
+ return false;
+ if (!isVideo && haveVoicePlugins_)
+ return true;
+ if (isVideo && haveVideoPlugins_)
+ return true;
- if (isVideo) {
- // load qmlglsink to register GStreamer's GstGLVideoItem QML type
- GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr);
- gst_object_unref(qmlglsink);
+ const gchar *voicePlugins[] = {"audioconvert",
+ "audioresample",
+ "autodetect",
+ "dtls",
+ "nice",
+ "opus",
+ "playback",
+ "rtpmanager",
+ "srtp",
+ "volume",
+ "webrtc",
+ nullptr};
+
+ const gchar *videoPlugins[] = {
+ "compositor", "opengl", "qmlgl", "rtp", "videoconvert", "vpx", nullptr};
+
+ std::string strError("Missing GStreamer plugins: ");
+ const gchar **needed = isVideo ? videoPlugins : voicePlugins;
+ bool &havePlugins = isVideo ? haveVideoPlugins_ : haveVoicePlugins_;
+ havePlugins = true;
+ GstRegistry *registry = gst_registry_get();
+ for (guint i = 0; i < g_strv_length((gchar **)needed); i++) {
+ GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
+ if (!plugin) {
+ havePlugins = false;
+ strError += std::string(needed[i]) + " ";
+ continue;
}
- return true;
+ gst_object_unref(plugin);
+ }
+ if (!havePlugins) {
+ nhlog::ui()->error(strError);
+ if (errorMessage)
+ *errorMessage = strError;
+ return false;
+ }
+
+ if (isVideo) {
+ // load qmlglsink to register GStreamer's GstGLVideoItem QML type
+ GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr);
+ gst_object_unref(qmlglsink);
+ }
+ return true;
}
bool
WebRTCSession::createOffer(CallType callType, uint32_t shareWindowId)
{
- clear();
- isOffering_ = true;
- callType_ = callType;
- shareWindowId_ = shareWindowId;
-
- // opus and vp8 rtp payload types must be defined dynamically
- // therefore from the range [96-127]
- // see for example https://tools.ietf.org/html/rfc7587
- constexpr int opusPayloadType = 111;
- constexpr int vp8PayloadType = 96;
- return startPipeline(opusPayloadType, vp8PayloadType);
+ clear();
+ isOffering_ = true;
+ callType_ = callType;
+ shareWindowId_ = shareWindowId;
+
+ // opus and vp8 rtp payload types must be defined dynamically
+ // therefore from the range [96-127]
+ // see for example https://tools.ietf.org/html/rfc7587
+ constexpr int opusPayloadType = 111;
+ constexpr int vp8PayloadType = 96;
+ return startPipeline(opusPayloadType, vp8PayloadType);
}
bool
WebRTCSession::acceptOffer(const std::string &sdp)
{
- nhlog::ui()->debug("WebRTC: received offer:\n{}", sdp);
- if (state_ != State::DISCONNECTED)
- return false;
+ nhlog::ui()->debug("WebRTC: received offer:\n{}", sdp);
+ if (state_ != State::DISCONNECTED)
+ return false;
- clear();
- GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
- if (!offer)
- return false;
+ clear();
+ GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
+ if (!offer)
+ return false;
- int opusPayloadType;
- bool recvOnly;
- bool sendOnly;
- if (getMediaAttributes(offer->sdp, "audio", "opus", opusPayloadType, recvOnly, sendOnly)) {
- if (opusPayloadType == -1) {
- nhlog::ui()->error("WebRTC: remote audio offer - no opus encoding");
- gst_webrtc_session_description_free(offer);
- return false;
- }
- } else {
- nhlog::ui()->error("WebRTC: remote offer - no audio media");
- gst_webrtc_session_description_free(offer);
- return false;
+ int opusPayloadType;
+ bool recvOnly;
+ bool sendOnly;
+ if (getMediaAttributes(offer->sdp, "audio", "opus", opusPayloadType, recvOnly, sendOnly)) {
+ if (opusPayloadType == -1) {
+ nhlog::ui()->error("WebRTC: remote audio offer - no opus encoding");
+ gst_webrtc_session_description_free(offer);
+ return false;
}
+ } else {
+ nhlog::ui()->error("WebRTC: remote offer - no audio media");
+ gst_webrtc_session_description_free(offer);
+ return false;
+ }
- int vp8PayloadType;
- bool isVideo = getMediaAttributes(offer->sdp,
- "video",
- "vp8",
- vp8PayloadType,
- isRemoteVideoRecvOnly_,
- isRemoteVideoSendOnly_);
- if (isVideo && vp8PayloadType == -1) {
- nhlog::ui()->error("WebRTC: remote video offer - no vp8 encoding");
- gst_webrtc_session_description_free(offer);
- return false;
- }
- callType_ = isVideo ? CallType::VIDEO : CallType::VOICE;
+ int vp8PayloadType;
+ bool isVideo = getMediaAttributes(
+ offer->sdp, "video", "vp8", vp8PayloadType, isRemoteVideoRecvOnly_, isRemoteVideoSendOnly_);
+ if (isVideo && vp8PayloadType == -1) {
+ nhlog::ui()->error("WebRTC: remote video offer - no vp8 encoding");
+ gst_webrtc_session_description_free(offer);
+ return false;
+ }
+ callType_ = isVideo ? CallType::VIDEO : CallType::VOICE;
- if (!startPipeline(opusPayloadType, vp8PayloadType)) {
- gst_webrtc_session_description_free(offer);
- return false;
- }
+ if (!startPipeline(opusPayloadType, vp8PayloadType)) {
+ gst_webrtc_session_description_free(offer);
+ return false;
+ }
- // avoid a race that sometimes leaves the generated answer without media tracks (a=ssrc
- // lines)
- std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ // avoid a race that sometimes leaves the generated answer without media tracks (a=ssrc
+ // lines)
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
- // set-remote-description first, then create-answer
- GstPromise *promise = gst_promise_new_with_change_func(createAnswer, webrtc_, nullptr);
- g_signal_emit_by_name(webrtc_, "set-remote-description", offer, promise);
- gst_webrtc_session_description_free(offer);
- return true;
+ // set-remote-description first, then create-answer
+ GstPromise *promise = gst_promise_new_with_change_func(createAnswer, webrtc_, nullptr);
+ g_signal_emit_by_name(webrtc_, "set-remote-description", offer, promise);
+ gst_webrtc_session_description_free(offer);
+ return true;
}
bool
WebRTCSession::acceptAnswer(const std::string &sdp)
{
- nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
- if (state_ != State::OFFERSENT)
- return false;
-
- GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
- if (!answer) {
- end();
- return false;
- }
-
- if (callType_ != CallType::VOICE) {
- int unused;
- if (!getMediaAttributes(answer->sdp,
- "video",
- "vp8",
- unused,
- isRemoteVideoRecvOnly_,
- isRemoteVideoSendOnly_))
- isRemoteVideoRecvOnly_ = true;
- }
+ nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
+ if (state_ != State::OFFERSENT)
+ return false;
- g_signal_emit_by_name(webrtc_, "set-remote-description", answer, nullptr);
- gst_webrtc_session_description_free(answer);
- return true;
+ GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
+ if (!answer) {
+ end();
+ return false;
+ }
+
+ if (callType_ != CallType::VOICE) {
+ int unused;
+ if (!getMediaAttributes(
+ answer->sdp, "video", "vp8", unused, isRemoteVideoRecvOnly_, isRemoteVideoSendOnly_))
+ isRemoteVideoRecvOnly_ = true;
+ }
+
+ g_signal_emit_by_name(webrtc_, "set-remote-description", answer, nullptr);
+ gst_webrtc_session_description_free(answer);
+ return true;
}
void
WebRTCSession::acceptICECandidates(
const std::vector<mtx::events::msg::CallCandidates::Candidate> &candidates)
{
- if (state_ >= State::INITIATED) {
- for (const auto &c : candidates) {
- nhlog::ui()->debug(
- "WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
- if (!c.candidate.empty()) {
- g_signal_emit_by_name(webrtc_,
- "add-ice-candidate",
- c.sdpMLineIndex,
- c.candidate.c_str());
- }
- }
+ if (state_ >= State::INITIATED) {
+ for (const auto &c : candidates) {
+ nhlog::ui()->debug(
+ "WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
+ if (!c.candidate.empty()) {
+ g_signal_emit_by_name(
+ webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
+ }
}
+ }
}
bool
WebRTCSession::startPipeline(int opusPayloadType, int vp8PayloadType)
{
- if (state_ != State::DISCONNECTED)
- return false;
-
- emit stateChanged(State::INITIATING);
-
- if (!createPipeline(opusPayloadType, vp8PayloadType)) {
- end();
- return false;
- }
-
- webrtc_ = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
-
- if (ChatPage::instance()->userSettings()->useStunServer()) {
- nhlog::ui()->info("WebRTC: setting STUN server: {}", STUN_SERVER);
- g_object_set(webrtc_, "stun-server", STUN_SERVER, nullptr);
- }
-
- for (const auto &uri : turnServers_) {
- nhlog::ui()->info("WebRTC: setting TURN server: {}", uri);
- gboolean udata;
- g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
- }
- if (turnServers_.empty())
- nhlog::ui()->warn("WebRTC: no TURN server provided");
-
- // generate the offer when the pipeline goes to PLAYING
- if (isOffering_)
- g_signal_connect(
- webrtc_, "on-negotiation-needed", G_CALLBACK(::createOffer), nullptr);
-
- // on-ice-candidate is emitted when a local ICE candidate has been gathered
- g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr);
-
- // capture ICE failure
- g_signal_connect(
- webrtc_, "notify::ice-connection-state", G_CALLBACK(iceConnectionStateChanged), nullptr);
-
- // incoming streams trigger pad-added
- gst_element_set_state(pipe_, GST_STATE_READY);
- g_signal_connect(webrtc_, "pad-added", G_CALLBACK(addDecodeBin), pipe_);
-
- // capture ICE gathering completion
- g_signal_connect(
- webrtc_, "notify::ice-gathering-state", G_CALLBACK(iceGatheringStateChanged), nullptr);
+ if (state_ != State::DISCONNECTED)
+ return false;
- // webrtcbin lifetime is the same as that of the pipeline
- gst_object_unref(webrtc_);
+ emit stateChanged(State::INITIATING);
- // start the pipeline
- GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
- if (ret == GST_STATE_CHANGE_FAILURE) {
- nhlog::ui()->error("WebRTC: unable to start pipeline");
- end();
- return false;
- }
+ if (!createPipeline(opusPayloadType, vp8PayloadType)) {
+ end();
+ return false;
+ }
+
+ webrtc_ = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
+
+ if (ChatPage::instance()->userSettings()->useStunServer()) {
+ nhlog::ui()->info("WebRTC: setting STUN server: {}", STUN_SERVER);
+ g_object_set(webrtc_, "stun-server", STUN_SERVER, nullptr);
+ }
+
+ for (const auto &uri : turnServers_) {
+ nhlog::ui()->info("WebRTC: setting TURN server: {}", uri);
+ gboolean udata;
+ g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
+ }
+ if (turnServers_.empty())
+ nhlog::ui()->warn("WebRTC: no TURN server provided");
+
+ // generate the offer when the pipeline goes to PLAYING
+ if (isOffering_)
+ g_signal_connect(webrtc_, "on-negotiation-needed", G_CALLBACK(::createOffer), nullptr);
+
+ // on-ice-candidate is emitted when a local ICE candidate has been gathered
+ g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr);
+
+ // capture ICE failure
+ g_signal_connect(
+ webrtc_, "notify::ice-connection-state", G_CALLBACK(iceConnectionStateChanged), nullptr);
+
+ // incoming streams trigger pad-added
+ gst_element_set_state(pipe_, GST_STATE_READY);
+ g_signal_connect(webrtc_, "pad-added", G_CALLBACK(addDecodeBin), pipe_);
+
+ // capture ICE gathering completion
+ g_signal_connect(
+ webrtc_, "notify::ice-gathering-state", G_CALLBACK(iceGatheringStateChanged), nullptr);
+
+ // webrtcbin lifetime is the same as that of the pipeline
+ gst_object_unref(webrtc_);
+
+ // start the pipeline
+ GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ nhlog::ui()->error("WebRTC: unable to start pipeline");
+ end();
+ return false;
+ }
- GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
- busWatchId_ = gst_bus_add_watch(bus, newBusMessage, this);
- gst_object_unref(bus);
- emit stateChanged(State::INITIATED);
- return true;
+ GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
+ busWatchId_ = gst_bus_add_watch(bus, newBusMessage, this);
+ gst_object_unref(bus);
+ emit stateChanged(State::INITIATED);
+ return true;
}
bool
WebRTCSession::createPipeline(int opusPayloadType, int vp8PayloadType)
{
- GstDevice *device = devices_.audioDevice();
- if (!device)
- return false;
-
- GstElement *source = gst_device_create_element(device, nullptr);
- GstElement *volume = gst_element_factory_make("volume", "srclevel");
- GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
- GstElement *resample = gst_element_factory_make("audioresample", nullptr);
- GstElement *queue1 = gst_element_factory_make("queue", nullptr);
- GstElement *opusenc = gst_element_factory_make("opusenc", nullptr);
- GstElement *rtp = gst_element_factory_make("rtpopuspay", nullptr);
- GstElement *queue2 = gst_element_factory_make("queue", nullptr);
- GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
+ GstDevice *device = devices_.audioDevice();
+ if (!device)
+ return false;
- GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
- "media",
- G_TYPE_STRING,
- "audio",
- "encoding-name",
- G_TYPE_STRING,
- "OPUS",
- "payload",
- G_TYPE_INT,
- opusPayloadType,
- nullptr);
- g_object_set(capsfilter, "caps", rtpcaps, nullptr);
- gst_caps_unref(rtpcaps);
-
- GstElement *webrtcbin = gst_element_factory_make("webrtcbin", "webrtcbin");
- g_object_set(webrtcbin, "bundle-policy", GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE, nullptr);
-
- pipe_ = gst_pipeline_new(nullptr);
- gst_bin_add_many(GST_BIN(pipe_),
- source,
- volume,
- convert,
- resample,
- queue1,
- opusenc,
- rtp,
- queue2,
- capsfilter,
- webrtcbin,
- nullptr);
-
- if (!gst_element_link_many(source,
- volume,
- convert,
- resample,
- queue1,
- opusenc,
- rtp,
- queue2,
- capsfilter,
- webrtcbin,
- nullptr)) {
- nhlog::ui()->error("WebRTC: failed to link audio pipeline elements");
- return false;
- }
+ GstElement *source = gst_device_create_element(device, nullptr);
+ GstElement *volume = gst_element_factory_make("volume", "srclevel");
+ GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
+ GstElement *resample = gst_element_factory_make("audioresample", nullptr);
+ GstElement *queue1 = gst_element_factory_make("queue", nullptr);
+ GstElement *opusenc = gst_element_factory_make("opusenc", nullptr);
+ GstElement *rtp = gst_element_factory_make("rtpopuspay", nullptr);
+ GstElement *queue2 = gst_element_factory_make("queue", nullptr);
+ GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
+
+ GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
+ "media",
+ G_TYPE_STRING,
+ "audio",
+ "encoding-name",
+ G_TYPE_STRING,
+ "OPUS",
+ "payload",
+ G_TYPE_INT,
+ opusPayloadType,
+ nullptr);
+ g_object_set(capsfilter, "caps", rtpcaps, nullptr);
+ gst_caps_unref(rtpcaps);
+
+ GstElement *webrtcbin = gst_element_factory_make("webrtcbin", "webrtcbin");
+ g_object_set(webrtcbin, "bundle-policy", GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE, nullptr);
+
+ pipe_ = gst_pipeline_new(nullptr);
+ gst_bin_add_many(GST_BIN(pipe_),
+ source,
+ volume,
+ convert,
+ resample,
+ queue1,
+ opusenc,
+ rtp,
+ queue2,
+ capsfilter,
+ webrtcbin,
+ nullptr);
+
+ if (!gst_element_link_many(source,
+ volume,
+ convert,
+ resample,
+ queue1,
+ opusenc,
+ rtp,
+ queue2,
+ capsfilter,
+ webrtcbin,
+ nullptr)) {
+ nhlog::ui()->error("WebRTC: failed to link audio pipeline elements");
+ return false;
+ }
- return callType_ == CallType::VOICE || isRemoteVideoSendOnly_
- ? true
- : addVideoPipeline(vp8PayloadType);
+ return callType_ == CallType::VOICE || isRemoteVideoSendOnly_
+ ? true
+ : addVideoPipeline(vp8PayloadType);
}
bool
WebRTCSession::addVideoPipeline(int vp8PayloadType)
{
- // allow incoming video calls despite localUser having no webcam
- if (callType_ == CallType::VIDEO && !devices_.haveCamera())
- return !isOffering_;
-
- auto settings = ChatPage::instance()->userSettings();
- GstElement *camerafilter = nullptr;
- GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
- GstElement *tee = gst_element_factory_make("tee", "videosrctee");
- gst_bin_add_many(GST_BIN(pipe_), videoconvert, tee, nullptr);
- if (callType_ == CallType::VIDEO || (settings->screenSharePiP() && devices_.haveCamera())) {
- std::pair<int, int> resolution;
- std::pair<int, int> frameRate;
- GstDevice *device = devices_.videoDevice(resolution, frameRate);
- if (!device)
- return false;
-
- GstElement *camera = gst_device_create_element(device, nullptr);
- GstCaps *caps = gst_caps_new_simple("video/x-raw",
- "width",
- G_TYPE_INT,
- resolution.first,
- "height",
- G_TYPE_INT,
- resolution.second,
- "framerate",
- GST_TYPE_FRACTION,
- frameRate.first,
- frameRate.second,
- nullptr);
- camerafilter = gst_element_factory_make("capsfilter", "camerafilter");
- g_object_set(camerafilter, "caps", caps, nullptr);
- gst_caps_unref(caps);
-
- gst_bin_add_many(GST_BIN(pipe_), camera, camerafilter, nullptr);
- if (!gst_element_link_many(camera, videoconvert, camerafilter, nullptr)) {
- nhlog::ui()->error("WebRTC: failed to link camera elements");
- return false;
- }
- if (callType_ == CallType::VIDEO && !gst_element_link(camerafilter, tee)) {
- nhlog::ui()->error("WebRTC: failed to link camerafilter -> tee");
- return false;
- }
- }
+ // allow incoming video calls despite localUser having no webcam
+ if (callType_ == CallType::VIDEO && !devices_.haveCamera())
+ return !isOffering_;
+
+ auto settings = ChatPage::instance()->userSettings();
+ GstElement *camerafilter = nullptr;
+ GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
+ GstElement *tee = gst_element_factory_make("tee", "videosrctee");
+ gst_bin_add_many(GST_BIN(pipe_), videoconvert, tee, nullptr);
+ if (callType_ == CallType::VIDEO || (settings->screenSharePiP() && devices_.haveCamera())) {
+ std::pair<int, int> resolution;
+ std::pair<int, int> frameRate;
+ GstDevice *device = devices_.videoDevice(resolution, frameRate);
+ if (!device)
+ return false;
+
+ GstElement *camera = gst_device_create_element(device, nullptr);
+ GstCaps *caps = gst_caps_new_simple("video/x-raw",
+ "width",
+ G_TYPE_INT,
+ resolution.first,
+ "height",
+ G_TYPE_INT,
+ resolution.second,
+ "framerate",
+ GST_TYPE_FRACTION,
+ frameRate.first,
+ frameRate.second,
+ nullptr);
+ camerafilter = gst_element_factory_make("capsfilter", "camerafilter");
+ g_object_set(camerafilter, "caps", caps, nullptr);
+ gst_caps_unref(caps);
- if (callType_ == CallType::SCREEN) {
- nhlog::ui()->debug("WebRTC: screen share frame rate: {} fps",
- settings->screenShareFrameRate());
- nhlog::ui()->debug("WebRTC: screen share picture-in-picture: {}",
- settings->screenSharePiP());
- nhlog::ui()->debug("WebRTC: screen share request remote camera: {}",
- settings->screenShareRemoteVideo());
- nhlog::ui()->debug("WebRTC: screen share hide mouse cursor: {}",
- settings->screenShareHideCursor());
-
- GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "screenshare");
- if (!ximagesrc) {
- nhlog::ui()->error("WebRTC: failed to create ximagesrc");
- return false;
- }
- g_object_set(ximagesrc, "use-damage", FALSE, nullptr);
- g_object_set(ximagesrc, "xid", shareWindowId_, nullptr);
- g_object_set(
- ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr);
-
- GstCaps *caps = gst_caps_new_simple("video/x-raw",
- "framerate",
- GST_TYPE_FRACTION,
- settings->screenShareFrameRate(),
- 1,
- nullptr);
- GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
- g_object_set(capsfilter, "caps", caps, nullptr);
- gst_caps_unref(caps);
- gst_bin_add_many(GST_BIN(pipe_), ximagesrc, capsfilter, nullptr);
-
- if (settings->screenSharePiP() && devices_.haveCamera()) {
- GstElement *compositor = gst_element_factory_make("compositor", nullptr);
- g_object_set(compositor, "background", 1, nullptr);
- gst_bin_add(GST_BIN(pipe_), compositor);
- if (!gst_element_link_many(
- ximagesrc, compositor, capsfilter, tee, nullptr)) {
- nhlog::ui()->error("WebRTC: failed to link screen share elements");
- return false;
- }
-
- GstPad *srcpad = gst_element_get_static_pad(camerafilter, "src");
- remotePiPSinkPad_ = gst_element_get_request_pad(compositor, "sink_%u");
- if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, remotePiPSinkPad_))) {
- nhlog::ui()->error(
- "WebRTC: failed to link camerafilter -> compositor");
- gst_object_unref(srcpad);
- return false;
- }
- gst_object_unref(srcpad);
- } else if (!gst_element_link_many(
- ximagesrc, videoconvert, capsfilter, tee, nullptr)) {
- nhlog::ui()->error("WebRTC: failed to link screen share elements");
- return false;
- }
+ gst_bin_add_many(GST_BIN(pipe_), camera, camerafilter, nullptr);
+ if (!gst_element_link_many(camera, videoconvert, camerafilter, nullptr)) {
+ nhlog::ui()->error("WebRTC: failed to link camera elements");
+ return false;
}
-
- GstElement *queue = gst_element_factory_make("queue", nullptr);
- GstElement *vp8enc = gst_element_factory_make("vp8enc", nullptr);
- g_object_set(vp8enc, "deadline", 1, nullptr);
- g_object_set(vp8enc, "error-resilient", 1, nullptr);
- GstElement *rtpvp8pay = gst_element_factory_make("rtpvp8pay", nullptr);
- GstElement *rtpqueue = gst_element_factory_make("queue", nullptr);
- GstElement *rtpcapsfilter = gst_element_factory_make("capsfilter", nullptr);
- GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
- "media",
- G_TYPE_STRING,
- "video",
- "encoding-name",
- G_TYPE_STRING,
- "VP8",
- "payload",
- G_TYPE_INT,
- vp8PayloadType,
- nullptr);
- g_object_set(rtpcapsfilter, "caps", rtpcaps, nullptr);
- gst_caps_unref(rtpcaps);
-
- gst_bin_add_many(
- GST_BIN(pipe_), queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, nullptr);
-
- GstElement *webrtcbin = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
- if (!gst_element_link_many(
- tee, queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, webrtcbin, nullptr)) {
- nhlog::ui()->error("WebRTC: failed to link rtp video elements");
- gst_object_unref(webrtcbin);
- return false;
+ if (callType_ == CallType::VIDEO && !gst_element_link(camerafilter, tee)) {
+ nhlog::ui()->error("WebRTC: failed to link camerafilter -> tee");
+ return false;
}
-
- if (callType_ == CallType::SCREEN &&
- !ChatPage::instance()->userSettings()->screenShareRemoteVideo()) {
- GArray *transceivers;
- g_signal_emit_by_name(webrtcbin, "get-transceivers", &transceivers);
- GstWebRTCRTPTransceiver *transceiver =
- g_array_index(transceivers, GstWebRTCRTPTransceiver *, 1);
- transceiver->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
- g_array_unref(transceivers);
+ }
+
+ if (callType_ == CallType::SCREEN) {
+ nhlog::ui()->debug("WebRTC: screen share frame rate: {} fps",
+ settings->screenShareFrameRate());
+ nhlog::ui()->debug("WebRTC: screen share picture-in-picture: {}",
+ settings->screenSharePiP());
+ nhlog::ui()->debug("WebRTC: screen share request remote camera: {}",
+ settings->screenShareRemoteVideo());
+ nhlog::ui()->debug("WebRTC: screen share hide mouse cursor: {}",
+ settings->screenShareHideCursor());
+
+ GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "screenshare");
+ if (!ximagesrc) {
+ nhlog::ui()->error("WebRTC: failed to create ximagesrc");
+ return false;
}
+ g_object_set(ximagesrc, "use-damage", FALSE, nullptr);
+ g_object_set(ximagesrc, "xid", shareWindowId_, nullptr);
+ g_object_set(ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr);
+
+ GstCaps *caps = gst_caps_new_simple("video/x-raw",
+ "framerate",
+ GST_TYPE_FRACTION,
+ settings->screenShareFrameRate(),
+ 1,
+ nullptr);
+ GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
+ g_object_set(capsfilter, "caps", caps, nullptr);
+ gst_caps_unref(caps);
+ gst_bin_add_many(GST_BIN(pipe_), ximagesrc, capsfilter, nullptr);
+
+ if (settings->screenSharePiP() && devices_.haveCamera()) {
+ GstElement *compositor = gst_element_factory_make("compositor", nullptr);
+ g_object_set(compositor, "background", 1, nullptr);
+ gst_bin_add(GST_BIN(pipe_), compositor);
+ if (!gst_element_link_many(ximagesrc, compositor, capsfilter, tee, nullptr)) {
+ nhlog::ui()->error("WebRTC: failed to link screen share elements");
+ return false;
+ }
+ GstPad *srcpad = gst_element_get_static_pad(camerafilter, "src");
+ remotePiPSinkPad_ = gst_element_get_request_pad(compositor, "sink_%u");
+ if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, remotePiPSinkPad_))) {
+ nhlog::ui()->error("WebRTC: failed to link camerafilter -> compositor");
+ gst_object_unref(srcpad);
+ return false;
+ }
+ gst_object_unref(srcpad);
+ } else if (!gst_element_link_many(ximagesrc, videoconvert, capsfilter, tee, nullptr)) {
+ nhlog::ui()->error("WebRTC: failed to link screen share elements");
+ return false;
+ }
+ }
+
+ GstElement *queue = gst_element_factory_make("queue", nullptr);
+ GstElement *vp8enc = gst_element_factory_make("vp8enc", nullptr);
+ g_object_set(vp8enc, "deadline", 1, nullptr);
+ g_object_set(vp8enc, "error-resilient", 1, nullptr);
+ GstElement *rtpvp8pay = gst_element_factory_make("rtpvp8pay", nullptr);
+ GstElement *rtpqueue = gst_element_factory_make("queue", nullptr);
+ GstElement *rtpcapsfilter = gst_element_factory_make("capsfilter", nullptr);
+ GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
+ "media",
+ G_TYPE_STRING,
+ "video",
+ "encoding-name",
+ G_TYPE_STRING,
+ "VP8",
+ "payload",
+ G_TYPE_INT,
+ vp8PayloadType,
+ nullptr);
+ g_object_set(rtpcapsfilter, "caps", rtpcaps, nullptr);
+ gst_caps_unref(rtpcaps);
+
+ gst_bin_add_many(GST_BIN(pipe_), queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, nullptr);
+
+ GstElement *webrtcbin = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
+ if (!gst_element_link_many(
+ tee, queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, webrtcbin, nullptr)) {
+ nhlog::ui()->error("WebRTC: failed to link rtp video elements");
gst_object_unref(webrtcbin);
- return true;
+ return false;
+ }
+
+ if (callType_ == CallType::SCREEN &&
+ !ChatPage::instance()->userSettings()->screenShareRemoteVideo()) {
+ GArray *transceivers;
+ g_signal_emit_by_name(webrtcbin, "get-transceivers", &transceivers);
+ GstWebRTCRTPTransceiver *transceiver =
+ g_array_index(transceivers, GstWebRTCRTPTransceiver *, 1);
+ transceiver->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
+ g_array_unref(transceivers);
+ }
+
+ gst_object_unref(webrtcbin);
+ return true;
}
bool
WebRTCSession::haveLocalPiP() const
{
- if (state_ >= State::INITIATED) {
- if (callType_ == CallType::VOICE || isRemoteVideoRecvOnly_)
- return false;
- else if (callType_ == CallType::SCREEN)
- return true;
- else {
- GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe_), "videosrctee");
- if (tee) {
- gst_object_unref(tee);
- return true;
- }
- }
+ if (state_ >= State::INITIATED) {
+ if (callType_ == CallType::VOICE || isRemoteVideoRecvOnly_)
+ return false;
+ else if (callType_ == CallType::SCREEN)
+ return true;
+ else {
+ GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe_), "videosrctee");
+ if (tee) {
+ gst_object_unref(tee);
+ return true;
+ }
}
- return false;
+ }
+ return false;
}
bool
WebRTCSession::isMicMuted() const
{
- if (state_ < State::INITIATED)
- return false;
+ if (state_ < State::INITIATED)
+ return false;
- GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
- gboolean muted;
- g_object_get(srclevel, "mute", &muted, nullptr);
- gst_object_unref(srclevel);
- return muted;
+ GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
+ gboolean muted;
+ g_object_get(srclevel, "mute", &muted, nullptr);
+ gst_object_unref(srclevel);
+ return muted;
}
bool
WebRTCSession::toggleMicMute()
{
- if (state_ < State::INITIATED)
- return false;
+ if (state_ < State::INITIATED)
+ return false;
- GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
- gboolean muted;
- g_object_get(srclevel, "mute", &muted, nullptr);
- g_object_set(srclevel, "mute", !muted, nullptr);
- gst_object_unref(srclevel);
- return !muted;
+ GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
+ gboolean muted;
+ g_object_get(srclevel, "mute", &muted, nullptr);
+ g_object_set(srclevel, "mute", !muted, nullptr);
+ gst_object_unref(srclevel);
+ return !muted;
}
void
WebRTCSession::toggleLocalPiP()
{
- if (localPiPSinkPad_) {
- guint zorder;
- g_object_get(localPiPSinkPad_, "zorder", &zorder, nullptr);
- g_object_set(localPiPSinkPad_, "zorder", zorder ? 0 : 2, nullptr);
- }
+ if (localPiPSinkPad_) {
+ guint zorder;
+ g_object_get(localPiPSinkPad_, "zorder", &zorder, nullptr);
+ g_object_set(localPiPSinkPad_, "zorder", zorder ? 0 : 2, nullptr);
+ }
}
void
WebRTCSession::clear()
{
- callType_ = webrtc::CallType::VOICE;
- isOffering_ = false;
- isRemoteVideoRecvOnly_ = false;
- isRemoteVideoSendOnly_ = false;
- videoItem_ = nullptr;
- pipe_ = nullptr;
- webrtc_ = nullptr;
- busWatchId_ = 0;
- shareWindowId_ = 0;
- haveAudioStream_ = false;
- haveVideoStream_ = false;
- localPiPSinkPad_ = nullptr;
- remotePiPSinkPad_ = nullptr;
- localsdp_.clear();
- localcandidates_.clear();
+ callType_ = webrtc::CallType::VOICE;
+ isOffering_ = false;
+ isRemoteVideoRecvOnly_ = false;
+ isRemoteVideoSendOnly_ = false;
+ videoItem_ = nullptr;
+ pipe_ = nullptr;
+ webrtc_ = nullptr;
+ busWatchId_ = 0;
+ shareWindowId_ = 0;
+ haveAudioStream_ = false;
+ haveVideoStream_ = false;
+ localPiPSinkPad_ = nullptr;
+ remotePiPSinkPad_ = nullptr;
+ localsdp_.clear();
+ localcandidates_.clear();
}
void
WebRTCSession::end()
{
- nhlog::ui()->debug("WebRTC: ending session");
- keyFrameRequestData_ = KeyFrameRequestData{};
- if (pipe_) {
- gst_element_set_state(pipe_, GST_STATE_NULL);
- gst_object_unref(pipe_);
- pipe_ = nullptr;
- if (busWatchId_) {
- g_source_remove(busWatchId_);
- busWatchId_ = 0;
- }
+ nhlog::ui()->debug("WebRTC: ending session");
+ keyFrameRequestData_ = KeyFrameRequestData{};
+ if (pipe_) {
+ gst_element_set_state(pipe_, GST_STATE_NULL);
+ gst_object_unref(pipe_);
+ pipe_ = nullptr;
+ if (busWatchId_) {
+ g_source_remove(busWatchId_);
+ busWatchId_ = 0;
}
+ }
- clear();
- if (state_ != State::DISCONNECTED)
- emit stateChanged(State::DISCONNECTED);
+ clear();
+ if (state_ != State::DISCONNECTED)
+ emit stateChanged(State::DISCONNECTED);
}
#else
@@ -1135,13 +1105,13 @@ WebRTCSession::end()
bool
WebRTCSession::havePlugins(bool, std::string *)
{
- return false;
+ return false;
}
bool
WebRTCSession::haveLocalPiP() const
{
- return false;
+ return false;
}
bool WebRTCSession::createOffer(webrtc::CallType, uint32_t) { return false; }
@@ -1149,13 +1119,13 @@ bool WebRTCSession::createOffer(webrtc::CallType, uint32_t) { return false; }
bool
WebRTCSession::acceptOffer(const std::string &)
{
- return false;
+ return false;
}
bool
WebRTCSession::acceptAnswer(const std::string &)
{
- return false;
+ return false;
}
void
@@ -1165,13 +1135,13 @@ WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandi
bool
WebRTCSession::isMicMuted() const
{
- return false;
+ return false;
}
bool
WebRTCSession::toggleMicMute()
{
- return false;
+ return false;
}
void
diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h
index 97487c5c..56c0a295 100644
--- a/src/WebRTCSession.h
+++ b/src/WebRTCSession.h
@@ -20,22 +20,22 @@ Q_NAMESPACE
enum class CallType
{
- VOICE,
- VIDEO,
- SCREEN // localUser is sharing screen
+ VOICE,
+ VIDEO,
+ SCREEN // localUser is sharing screen
};
Q_ENUM_NS(CallType)
enum class State
{
- DISCONNECTED,
- ICEFAILED,
- INITIATING,
- INITIATED,
- OFFERSENT,
- ANSWERSENT,
- CONNECTING,
- CONNECTED
+ DISCONNECTED,
+ ICEFAILED,
+ INITIATING,
+ INITIATED,
+ OFFERSENT,
+ ANSWERSENT,
+ CONNECTING,
+ CONNECTED
};
Q_ENUM_NS(State)
@@ -43,75 +43,75 @@ Q_ENUM_NS(State)
class WebRTCSession : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
- static WebRTCSession &instance()
- {
- static WebRTCSession instance;
- return instance;
- }
-
- bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
- webrtc::CallType callType() const { return callType_; }
- webrtc::State state() const { return state_; }
- bool haveLocalPiP() const;
- bool isOffering() const { return isOffering_; }
- bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
- bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; }
-
- bool createOffer(webrtc::CallType, uint32_t shareWindowId);
- bool acceptOffer(const std::string &sdp);
- bool acceptAnswer(const std::string &sdp);
- void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
-
- bool isMicMuted() const;
- bool toggleMicMute();
- void toggleLocalPiP();
- void end();
-
- void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
-
- void setVideoItem(QQuickItem *item) { videoItem_ = item; }
- QQuickItem *getVideoItem() const { return videoItem_; }
+ static WebRTCSession &instance()
+ {
+ static WebRTCSession instance;
+ return instance;
+ }
+
+ bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
+ webrtc::CallType callType() const { return callType_; }
+ webrtc::State state() const { return state_; }
+ bool haveLocalPiP() const;
+ bool isOffering() const { return isOffering_; }
+ bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
+ bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; }
+
+ bool createOffer(webrtc::CallType, uint32_t shareWindowId);
+ bool acceptOffer(const std::string &sdp);
+ bool acceptAnswer(const std::string &sdp);
+ void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
+
+ bool isMicMuted() const;
+ bool toggleMicMute();
+ void toggleLocalPiP();
+ void end();
+
+ void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
+
+ void setVideoItem(QQuickItem *item) { videoItem_ = item; }
+ QQuickItem *getVideoItem() const { return videoItem_; }
signals:
- void offerCreated(const std::string &sdp,
- const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
- void answerCreated(const std::string &sdp,
- const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
- void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
- void stateChanged(webrtc::State);
+ void offerCreated(const std::string &sdp,
+ const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
+ void answerCreated(const std::string &sdp,
+ const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
+ void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
+ void stateChanged(webrtc::State);
private slots:
- void setState(webrtc::State state) { state_ = state; }
+ void setState(webrtc::State state) { state_ = state; }
private:
- WebRTCSession();
-
- CallDevices &devices_;
- bool initialised_ = false;
- bool haveVoicePlugins_ = false;
- bool haveVideoPlugins_ = false;
- webrtc::CallType callType_ = webrtc::CallType::VOICE;
- webrtc::State state_ = webrtc::State::DISCONNECTED;
- bool isOffering_ = false;
- bool isRemoteVideoRecvOnly_ = false;
- bool isRemoteVideoSendOnly_ = false;
- QQuickItem *videoItem_ = nullptr;
- GstElement *pipe_ = nullptr;
- GstElement *webrtc_ = nullptr;
- unsigned int busWatchId_ = 0;
- std::vector<std::string> turnServers_;
- uint32_t shareWindowId_ = 0;
-
- bool init(std::string *errorMessage = nullptr);
- bool startPipeline(int opusPayloadType, int vp8PayloadType);
- bool createPipeline(int opusPayloadType, int vp8PayloadType);
- bool addVideoPipeline(int vp8PayloadType);
- void clear();
+ WebRTCSession();
+
+ CallDevices &devices_;
+ bool initialised_ = false;
+ bool haveVoicePlugins_ = false;
+ bool haveVideoPlugins_ = false;
+ webrtc::CallType callType_ = webrtc::CallType::VOICE;
+ webrtc::State state_ = webrtc::State::DISCONNECTED;
+ bool isOffering_ = false;
+ bool isRemoteVideoRecvOnly_ = false;
+ bool isRemoteVideoSendOnly_ = false;
+ QQuickItem *videoItem_ = nullptr;
+ GstElement *pipe_ = nullptr;
+ GstElement *webrtc_ = nullptr;
+ unsigned int busWatchId_ = 0;
+ std::vector<std::string> turnServers_;
+ uint32_t shareWindowId_ = 0;
+
+ bool init(std::string *errorMessage = nullptr);
+ bool startPipeline(int opusPayloadType, int vp8PayloadType);
+ bool createPipeline(int opusPayloadType, int vp8PayloadType);
+ bool addVideoPipeline(int vp8PayloadType);
+ void clear();
public:
- WebRTCSession(WebRTCSession const &) = delete;
- void operator=(WebRTCSession const &) = delete;
+ WebRTCSession(WebRTCSession const &) = delete;
+ void operator=(WebRTCSession const &) = delete;
};
diff --git a/src/WelcomePage.cpp b/src/WelcomePage.cpp
index 2cce7b8d..c7168789 100644
--- a/src/WelcomePage.cpp
+++ b/src/WelcomePage.cpp
@@ -16,72 +16,72 @@
WelcomePage::WelcomePage(QWidget *parent)
: QWidget(parent)
{
- auto topLayout_ = new QVBoxLayout(this);
- topLayout_->setSpacing(20);
- topLayout_->setAlignment(Qt::AlignCenter);
+ auto topLayout_ = new QVBoxLayout(this);
+ topLayout_->setSpacing(20);
+ topLayout_->setAlignment(Qt::AlignCenter);
- QFont headingFont;
- headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
- QFont subTitleFont;
- subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
+ QFont headingFont;
+ headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
+ QFont subTitleFont;
+ subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
- QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
+ QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
- auto logo_ = new QLabel(this);
- logo_->setPixmap(icon.pixmap(256));
- logo_->setAlignment(Qt::AlignCenter);
+ auto logo_ = new QLabel(this);
+ logo_->setPixmap(icon.pixmap(256));
+ logo_->setAlignment(Qt::AlignCenter);
- QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
- QString main(tr("Enjoy your stay!"));
+ QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
+ QString main(tr("Enjoy your stay!"));
- auto intoTxt_ = new TextLabel(heading, this);
- intoTxt_->setFont(headingFont);
- intoTxt_->setAlignment(Qt::AlignCenter);
+ auto intoTxt_ = new TextLabel(heading, this);
+ intoTxt_->setFont(headingFont);
+ intoTxt_->setAlignment(Qt::AlignCenter);
- auto subTitle = new TextLabel(main, this);
- subTitle->setFont(subTitleFont);
- subTitle->setAlignment(Qt::AlignCenter);
+ auto subTitle = new TextLabel(main, this);
+ subTitle->setFont(subTitleFont);
+ subTitle->setAlignment(Qt::AlignCenter);
- topLayout_->addStretch(1);
- topLayout_->addWidget(logo_);
- topLayout_->addWidget(intoTxt_);
- topLayout_->addWidget(subTitle);
+ topLayout_->addStretch(1);
+ topLayout_->addWidget(logo_);
+ topLayout_->addWidget(intoTxt_);
+ topLayout_->addWidget(subTitle);
- auto btnLayout_ = new QHBoxLayout();
- btnLayout_->setSpacing(20);
- btnLayout_->setContentsMargins(0, 20, 0, 20);
+ auto btnLayout_ = new QHBoxLayout();
+ btnLayout_->setSpacing(20);
+ btnLayout_->setContentsMargins(0, 20, 0, 20);
- const int fontHeight = QFontMetrics{subTitleFont}.height();
- const int buttonHeight = fontHeight * 2.5;
- const int buttonWidth = fontHeight * 8;
+ const int fontHeight = QFontMetrics{subTitleFont}.height();
+ const int buttonHeight = fontHeight * 2.5;
+ const int buttonWidth = fontHeight * 8;
- auto registerBtn = new RaisedButton(tr("REGISTER"), this);
- registerBtn->setMinimumSize(buttonWidth, buttonHeight);
- registerBtn->setFontSize(subTitleFont.pointSizeF());
- registerBtn->setCornerRadius(conf::btn::cornerRadius);
+ auto registerBtn = new RaisedButton(tr("REGISTER"), this);
+ registerBtn->setMinimumSize(buttonWidth, buttonHeight);
+ registerBtn->setFontSize(subTitleFont.pointSizeF());
+ registerBtn->setCornerRadius(conf::btn::cornerRadius);
- auto loginBtn = new RaisedButton(tr("LOGIN"), this);
- loginBtn->setMinimumSize(buttonWidth, buttonHeight);
- loginBtn->setFontSize(subTitleFont.pointSizeF());
- loginBtn->setCornerRadius(conf::btn::cornerRadius);
+ auto loginBtn = new RaisedButton(tr("LOGIN"), this);
+ loginBtn->setMinimumSize(buttonWidth, buttonHeight);
+ loginBtn->setFontSize(subTitleFont.pointSizeF());
+ loginBtn->setCornerRadius(conf::btn::cornerRadius);
- btnLayout_->addStretch(1);
- btnLayout_->addWidget(registerBtn);
- btnLayout_->addWidget(loginBtn);
- btnLayout_->addStretch(1);
+ btnLayout_->addStretch(1);
+ btnLayout_->addWidget(registerBtn);
+ btnLayout_->addWidget(loginBtn);
+ btnLayout_->addStretch(1);
- topLayout_->addLayout(btnLayout_);
- topLayout_->addStretch(1);
+ topLayout_->addLayout(btnLayout_);
+ topLayout_->addStretch(1);
- connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
- connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
+ connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
+ connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
}
void
WelcomePage::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/WelcomePage.h b/src/WelcomePage.h
index d2dcc0c9..aa531a03 100644
--- a/src/WelcomePage.h
+++ b/src/WelcomePage.h
@@ -8,18 +8,18 @@
class WelcomePage : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit WelcomePage(QWidget *parent = nullptr);
+ explicit WelcomePage(QWidget *parent = nullptr);
protected:
- void paintEvent(QPaintEvent *) override;
+ void paintEvent(QPaintEvent *) override;
signals:
- // Notify that the user wants to login in.
- void userLogin();
+ // Notify that the user wants to login in.
+ void userLogin();
- // Notify that the user wants to register.
- void userRegister();
+ // Notify that the user wants to register.
+ void userRegister();
};
diff --git a/src/dialogs/CreateRoom.cpp b/src/dialogs/CreateRoom.cpp
index ba385436..30dbf83d 100644
--- a/src/dialogs/CreateRoom.cpp
+++ b/src/dialogs/CreateRoom.cpp
@@ -18,142 +18,142 @@ using namespace dialogs;
CreateRoom::CreateRoom(QWidget *parent)
: QFrame(parent)
{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
-
- QFont largeFont;
- largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
-
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- setMinimumHeight(conf::modals::MIN_WIDGET_HEIGHT);
- setMinimumWidth(conf::window::minModalWidth);
-
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
-
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(15);
-
- confirmBtn_ = new QPushButton(tr("Create room"), this);
- confirmBtn_->setDefault(true);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
-
- buttonLayout->addStretch(1);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
-
- QFont font;
- font.setPointSizeF(font.pointSizeF() * 1.3);
-
- nameInput_ = new TextField(this);
- nameInput_->setLabel(tr("Name"));
-
- topicInput_ = new TextField(this);
- topicInput_->setLabel(tr("Topic"));
-
- aliasInput_ = new TextField(this);
- aliasInput_->setLabel(tr("Alias"));
-
- auto visibilityLayout = new QHBoxLayout;
- visibilityLayout->setContentsMargins(0, 10, 0, 10);
-
- auto presetLayout = new QHBoxLayout;
- presetLayout->setContentsMargins(0, 10, 0, 10);
-
- auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
- visibilityCombo_ = new QComboBox(this);
- visibilityCombo_->addItem("Private");
- visibilityCombo_->addItem("Public");
-
- visibilityLayout->addWidget(visibilityLabel);
- visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
-
- auto presetLabel = new QLabel(tr("Room Preset"), this);
- presetCombo_ = new QComboBox(this);
- presetCombo_->addItem("Private Chat");
- presetCombo_->addItem("Public Chat");
- presetCombo_->addItem("Trusted Private Chat");
-
- presetLayout->addWidget(presetLabel);
- presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
-
- auto directLabel_ = new QLabel(tr("Direct Chat"), this);
- directToggle_ = new Toggle(this);
- directToggle_->setActiveColor(QColor("#38A3D8"));
- directToggle_->setInactiveColor(QColor("gray"));
- directToggle_->setState(false);
-
- auto directLayout = new QHBoxLayout;
- directLayout->setContentsMargins(0, 10, 0, 10);
- directLayout->addWidget(directLabel_);
- directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
-
- layout->addWidget(nameInput_);
- layout->addWidget(topicInput_);
- layout->addWidget(aliasInput_);
- layout->addLayout(visibilityLayout);
- layout->addLayout(presetLayout);
- layout->addLayout(directLayout);
- layout->addLayout(buttonLayout);
-
- connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
- request_.name = nameInput_->text().toStdString();
- request_.topic = topicInput_->text().toStdString();
- request_.room_alias_name = aliasInput_->text().toStdString();
-
- emit createRoom(request_);
-
- clearFields();
- emit close();
- });
-
- connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
- clearFields();
- emit close();
- });
-
- connect(visibilityCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &text) {
- if (text == "Private") {
- request_.visibility = mtx::common::RoomVisibility::Private;
- } else {
- request_.visibility = mtx::common::RoomVisibility::Public;
- }
- });
-
- connect(presetCombo_,
- static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
- [this](const QString &text) {
- if (text == "Private Chat") {
- request_.preset = mtx::requests::Preset::PrivateChat;
- } else if (text == "Public Chat") {
- request_.preset = mtx::requests::Preset::PublicChat;
- } else {
- request_.preset = mtx::requests::Preset::TrustedPrivateChat;
- }
- });
-
- connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
- request_.is_direct = isEnabled;
- });
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+
+ QFont largeFont;
+ largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
+
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ setMinimumHeight(conf::modals::MIN_WIDGET_HEIGHT);
+ setMinimumWidth(conf::window::minModalWidth);
+
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(conf::modals::WIDGET_SPACING);
+ layout->setMargin(conf::modals::WIDGET_MARGIN);
+
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(15);
+
+ confirmBtn_ = new QPushButton(tr("Create room"), this);
+ confirmBtn_->setDefault(true);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
+
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(cancelBtn_);
+ buttonLayout->addWidget(confirmBtn_);
+
+ QFont font;
+ font.setPointSizeF(font.pointSizeF() * 1.3);
+
+ nameInput_ = new TextField(this);
+ nameInput_->setLabel(tr("Name"));
+
+ topicInput_ = new TextField(this);
+ topicInput_->setLabel(tr("Topic"));
+
+ aliasInput_ = new TextField(this);
+ aliasInput_->setLabel(tr("Alias"));
+
+ auto visibilityLayout = new QHBoxLayout;
+ visibilityLayout->setContentsMargins(0, 10, 0, 10);
+
+ auto presetLayout = new QHBoxLayout;
+ presetLayout->setContentsMargins(0, 10, 0, 10);
+
+ auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
+ visibilityCombo_ = new QComboBox(this);
+ visibilityCombo_->addItem("Private");
+ visibilityCombo_->addItem("Public");
+
+ visibilityLayout->addWidget(visibilityLabel);
+ visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
+
+ auto presetLabel = new QLabel(tr("Room Preset"), this);
+ presetCombo_ = new QComboBox(this);
+ presetCombo_->addItem("Private Chat");
+ presetCombo_->addItem("Public Chat");
+ presetCombo_->addItem("Trusted Private Chat");
+
+ presetLayout->addWidget(presetLabel);
+ presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
+
+ auto directLabel_ = new QLabel(tr("Direct Chat"), this);
+ directToggle_ = new Toggle(this);
+ directToggle_->setActiveColor(QColor("#38A3D8"));
+ directToggle_->setInactiveColor(QColor("gray"));
+ directToggle_->setState(false);
+
+ auto directLayout = new QHBoxLayout;
+ directLayout->setContentsMargins(0, 10, 0, 10);
+ directLayout->addWidget(directLabel_);
+ directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
+
+ layout->addWidget(nameInput_);
+ layout->addWidget(topicInput_);
+ layout->addWidget(aliasInput_);
+ layout->addLayout(visibilityLayout);
+ layout->addLayout(presetLayout);
+ layout->addLayout(directLayout);
+ layout->addLayout(buttonLayout);
+
+ connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
+ request_.name = nameInput_->text().toStdString();
+ request_.topic = topicInput_->text().toStdString();
+ request_.room_alias_name = aliasInput_->text().toStdString();
+
+ emit createRoom(request_);
+
+ clearFields();
+ emit close();
+ });
+
+ connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
+ clearFields();
+ emit close();
+ });
+
+ connect(visibilityCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &text) {
+ if (text == "Private") {
+ request_.visibility = mtx::common::RoomVisibility::Private;
+ } else {
+ request_.visibility = mtx::common::RoomVisibility::Public;
+ }
+ });
+
+ connect(presetCombo_,
+ static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
+ [this](const QString &text) {
+ if (text == "Private Chat") {
+ request_.preset = mtx::requests::Preset::PrivateChat;
+ } else if (text == "Public Chat") {
+ request_.preset = mtx::requests::Preset::PublicChat;
+ } else {
+ request_.preset = mtx::requests::Preset::TrustedPrivateChat;
+ }
+ });
+
+ connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
+ request_.is_direct = isEnabled;
+ });
}
void
CreateRoom::clearFields()
{
- nameInput_->clear();
- topicInput_->clear();
- aliasInput_->clear();
+ nameInput_->clear();
+ topicInput_->clear();
+ aliasInput_->clear();
}
void
CreateRoom::showEvent(QShowEvent *event)
{
- nameInput_->setFocus();
+ nameInput_->setFocus();
- QFrame::showEvent(event);
+ QFrame::showEvent(event);
}
diff --git a/src/dialogs/CreateRoom.h b/src/dialogs/CreateRoom.h
index d4c6474d..d9d90a10 100644
--- a/src/dialogs/CreateRoom.h
+++ b/src/dialogs/CreateRoom.h
@@ -17,32 +17,32 @@ namespace dialogs {
class CreateRoom : public QFrame
{
- Q_OBJECT
+ Q_OBJECT
public:
- CreateRoom(QWidget *parent = nullptr);
+ CreateRoom(QWidget *parent = nullptr);
signals:
- void createRoom(const mtx::requests::CreateRoom &request);
+ void createRoom(const mtx::requests::CreateRoom &request);
protected:
- void showEvent(QShowEvent *event) override;
+ void showEvent(QShowEvent *event) override;
private:
- void clearFields();
+ void clearFields();
- QComboBox *visibilityCombo_;
- QComboBox *presetCombo_;
+ QComboBox *visibilityCombo_;
+ QComboBox *presetCombo_;
- Toggle *directToggle_;
+ Toggle *directToggle_;
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *confirmBtn_;
+ QPushButton *cancelBtn_;
- TextField *nameInput_;
- TextField *topicInput_;
- TextField *aliasInput_;
+ TextField *nameInput_;
+ TextField *topicInput_;
+ TextField *aliasInput_;
- mtx::requests::CreateRoom request_;
+ mtx::requests::CreateRoom request_;
};
} // dialogs
diff --git a/src/dialogs/FallbackAuth.cpp b/src/dialogs/FallbackAuth.cpp
index c7b179f4..2b8dfed9 100644
--- a/src/dialogs/FallbackAuth.cpp
+++ b/src/dialogs/FallbackAuth.cpp
@@ -18,56 +18,56 @@ using namespace dialogs;
FallbackAuth::FallbackAuth(const QString &authType, const QString &session, QWidget *parent)
: QWidget(parent)
{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+ setAttribute(Qt::WA_DeleteOnClose, true);
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(conf::modals::WIDGET_SPACING);
+ layout->setMargin(conf::modals::WIDGET_MARGIN);
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(8);
- buttonLayout->setMargin(0);
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(8);
+ buttonLayout->setMargin(0);
- openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
- confirmBtn_ = new QPushButton(tr("Confirm"), this);
- confirmBtn_->setDefault(true);
+ openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
+ confirmBtn_ = new QPushButton(tr("Confirm"), this);
+ confirmBtn_->setDefault(true);
- buttonLayout->addStretch(1);
- buttonLayout->addWidget(openBtn_);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(openBtn_);
+ buttonLayout->addWidget(cancelBtn_);
+ buttonLayout->addWidget(confirmBtn_);
- QFont font;
- font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
+ QFont font;
+ font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
- auto label = new QLabel(
- tr("Open the fallback, follow the steps and confirm after completing them."), this);
- label->setFont(font);
+ auto label = new QLabel(
+ tr("Open the fallback, follow the steps and confirm after completing them."), this);
+ label->setFont(font);
- layout->addWidget(label);
- layout->addLayout(buttonLayout);
+ layout->addWidget(label);
+ layout->addLayout(buttonLayout);
- connect(openBtn_, &QPushButton::clicked, [session, authType]() {
- const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
- "fallback/web?session=%3")
- .arg(QString::fromStdString(http::client()->server()))
- .arg(http::client()->port())
- .arg(session)
- .arg(authType);
+ connect(openBtn_, &QPushButton::clicked, [session, authType]() {
+ const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
+ "fallback/web?session=%3")
+ .arg(QString::fromStdString(http::client()->server()))
+ .arg(http::client()->port())
+ .arg(session)
+ .arg(authType);
- QDesktopServices::openUrl(url);
- });
+ QDesktopServices::openUrl(url);
+ });
- connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
- emit confirmation();
- emit close();
- });
- connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
- emit cancel();
- emit close();
- });
+ connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
+ emit confirmation();
+ emit close();
+ });
+ connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
+ emit cancel();
+ emit close();
+ });
}
diff --git a/src/dialogs/FallbackAuth.h b/src/dialogs/FallbackAuth.h
index 8e4e28ea..6bfd59f7 100644
--- a/src/dialogs/FallbackAuth.h
+++ b/src/dialogs/FallbackAuth.h
@@ -13,18 +13,18 @@ namespace dialogs {
class FallbackAuth : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
+ FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
signals:
- void confirmation();
- void cancel();
+ void confirmation();
+ void cancel();
private:
- QPushButton *openBtn_;
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *openBtn_;
+ QPushButton *confirmBtn_;
+ QPushButton *cancelBtn_;
};
} // dialogs
diff --git a/src/dialogs/ImageOverlay.cpp b/src/dialogs/ImageOverlay.cpp
index 12813d57..8c90a744 100644
--- a/src/dialogs/ImageOverlay.cpp
+++ b/src/dialogs/ImageOverlay.cpp
@@ -19,84 +19,83 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)
: QWidget{parent}
, originalImage_{image}
{
- setMouseTracking(true);
- setParent(nullptr);
+ setMouseTracking(true);
+ setParent(nullptr);
- setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
+ setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
- setAttribute(Qt::WA_NoSystemBackground, true);
- setAttribute(Qt::WA_TranslucentBackground, true);
- setAttribute(Qt::WA_DeleteOnClose, true);
- setWindowState(Qt::WindowFullScreen);
- close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
+ setAttribute(Qt::WA_NoSystemBackground, true);
+ setAttribute(Qt::WA_TranslucentBackground, true);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ setWindowState(Qt::WindowFullScreen);
+ close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
- connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
- connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
+ connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
+ connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
- raise();
+ raise();
}
void
ImageOverlay::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
- // Full screen overlay.
- painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
+ // Full screen overlay.
+ painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
- // Left and Right margins
- int outer_margin = width() * 0.12;
- int buttonSize = 36;
- int margin = outer_margin * 0.1;
+ // Left and Right margins
+ int outer_margin = width() * 0.12;
+ int buttonSize = 36;
+ int margin = outer_margin * 0.1;
- int max_width = width() - 2 * outer_margin;
- int max_height = height();
+ int max_width = width() - 2 * outer_margin;
+ int max_height = height();
- image_ = utils::scaleDown(max_width, max_height, originalImage_);
+ image_ = utils::scaleDown(max_width, max_height, originalImage_);
- int diff_x = max_width - image_.width();
- int diff_y = max_height - image_.height();
+ int diff_x = max_width - image_.width();
+ int diff_y = max_height - image_.height();
- content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
- close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
- save_button_ =
- QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
+ content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
+ close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
+ save_button_ = QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
- // Draw main content_.
- painter.drawPixmap(content_, image_);
+ // Draw main content_.
+ painter.drawPixmap(content_, image_);
- // Draw top right corner X.
- QPen pen;
- pen.setCapStyle(Qt::RoundCap);
- pen.setWidthF(5);
- pen.setColor("gray");
+ // Draw top right corner X.
+ QPen pen;
+ pen.setCapStyle(Qt::RoundCap);
+ pen.setWidthF(5);
+ pen.setColor("gray");
- auto center = close_button_.center();
+ auto center = close_button_.center();
- painter.setPen(pen);
- painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
- painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
+ painter.setPen(pen);
+ painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
+ painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
- // Draw download button
- center = save_button_.center();
- painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
- painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
- painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
+ // Draw download button
+ center = save_button_.center();
+ painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
+ painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
+ painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
}
void
ImageOverlay::mousePressEvent(QMouseEvent *event)
{
- if (event->button() != Qt::LeftButton)
- return;
-
- if (close_button_.contains(event->pos()))
- emit closing();
- else if (save_button_.contains(event->pos()))
- emit saving();
- else if (!content_.contains(event->pos()))
- emit closing();
+ if (event->button() != Qt::LeftButton)
+ return;
+
+ if (close_button_.contains(event->pos()))
+ emit closing();
+ else if (save_button_.contains(event->pos()))
+ emit saving();
+ else if (!content_.contains(event->pos()))
+ emit closing();
}
diff --git a/src/dialogs/ImageOverlay.h b/src/dialogs/ImageOverlay.h
index 9d4187bf..2174279f 100644
--- a/src/dialogs/ImageOverlay.h
+++ b/src/dialogs/ImageOverlay.h
@@ -14,25 +14,25 @@ namespace dialogs {
class ImageOverlay : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- ImageOverlay(QPixmap image, QWidget *parent = nullptr);
+ ImageOverlay(QPixmap image, QWidget *parent = nullptr);
protected:
- void mousePressEvent(QMouseEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
signals:
- void closing();
- void saving();
+ void closing();
+ void saving();
private:
- QPixmap originalImage_;
- QPixmap image_;
+ QPixmap originalImage_;
+ QPixmap image_;
- QRect content_;
- QRect close_button_;
- QRect save_button_;
- QShortcut *close_shortcut_;
+ QRect content_;
+ QRect close_button_;
+ QRect save_button_;
+ QShortcut *close_shortcut_;
};
} // dialogs
diff --git a/src/dialogs/JoinRoom.cpp b/src/dialogs/JoinRoom.cpp
index dc2e4804..76baf857 100644
--- a/src/dialogs/JoinRoom.cpp
+++ b/src/dialogs/JoinRoom.cpp
@@ -16,58 +16,58 @@ using namespace dialogs;
JoinRoom::JoinRoom(QWidget *parent)
: QFrame(parent)
{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+ setAttribute(Qt::WA_DeleteOnClose, true);
- setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(conf::modals::WIDGET_SPACING);
+ layout->setMargin(conf::modals::WIDGET_MARGIN);
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(15);
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(15);
- confirmBtn_ = new QPushButton(tr("Join"), this);
- confirmBtn_->setDefault(true);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
+ confirmBtn_ = new QPushButton(tr("Join"), this);
+ confirmBtn_->setDefault(true);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
- buttonLayout->addStretch(1);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(cancelBtn_);
+ buttonLayout->addWidget(confirmBtn_);
- roomInput_ = new TextField(this);
- roomInput_->setLabel(tr("Room ID or alias"));
+ roomInput_ = new TextField(this);
+ roomInput_->setLabel(tr("Room ID or alias"));
- layout->addWidget(roomInput_);
- layout->addLayout(buttonLayout);
- layout->addStretch(1);
+ layout->addWidget(roomInput_);
+ layout->addLayout(buttonLayout);
+ layout->addStretch(1);
- connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput);
- connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput);
- connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close);
+ connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput);
+ connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput);
+ connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close);
}
void
JoinRoom::handleInput()
{
- if (roomInput_->text().isEmpty())
- return;
+ if (roomInput_->text().isEmpty())
+ return;
- // TODO: input validation with error messages.
- emit joinRoom(roomInput_->text());
- roomInput_->clear();
+ // TODO: input validation with error messages.
+ emit joinRoom(roomInput_->text());
+ roomInput_->clear();
- emit close();
+ emit close();
}
void
JoinRoom::showEvent(QShowEvent *event)
{
- roomInput_->setFocus();
+ roomInput_->setFocus();
- QFrame::showEvent(event);
+ QFrame::showEvent(event);
}
diff --git a/src/dialogs/JoinRoom.h b/src/dialogs/JoinRoom.h
index f399f1fb..11c54d7c 100644
--- a/src/dialogs/JoinRoom.h
+++ b/src/dialogs/JoinRoom.h
@@ -13,24 +13,24 @@ namespace dialogs {
class JoinRoom : public QFrame
{
- Q_OBJECT
+ Q_OBJECT
public:
- JoinRoom(QWidget *parent = nullptr);
+ JoinRoom(QWidget *parent = nullptr);
signals:
- void joinRoom(const QString &room);
+ void joinRoom(const QString &room);
protected:
- void showEvent(QShowEvent *event) override;
+ void showEvent(QShowEvent *event) override;
private slots:
- void handleInput();
+ void handleInput();
private:
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *confirmBtn_;
+ QPushButton *cancelBtn_;
- TextField *roomInput_;
+ TextField *roomInput_;
};
} // dialogs
diff --git a/src/dialogs/LeaveRoom.cpp b/src/dialogs/LeaveRoom.cpp
index 5246d693..9eb431da 100644
--- a/src/dialogs/LeaveRoom.cpp
+++ b/src/dialogs/LeaveRoom.cpp
@@ -15,39 +15,39 @@ using namespace dialogs;
LeaveRoom::LeaveRoom(QWidget *parent)
: QFrame(parent)
{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+ setAttribute(Qt::WA_DeleteOnClose, true);
- setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(conf::modals::WIDGET_SPACING);
+ layout->setMargin(conf::modals::WIDGET_MARGIN);
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(0);
- buttonLayout->setMargin(0);
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(0);
+ buttonLayout->setMargin(0);
- confirmBtn_ = new QPushButton("Leave", this);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
- cancelBtn_->setDefault(true);
+ confirmBtn_ = new QPushButton("Leave", this);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
+ cancelBtn_->setDefault(true);
- buttonLayout->addStretch(1);
- buttonLayout->setSpacing(15);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addStretch(1);
+ buttonLayout->setSpacing(15);
+ buttonLayout->addWidget(cancelBtn_);
+ buttonLayout->addWidget(confirmBtn_);
- auto label = new QLabel(tr("Are you sure you want to leave?"), this);
+ auto label = new QLabel(tr("Are you sure you want to leave?"), this);
- layout->addWidget(label);
- layout->addLayout(buttonLayout);
+ layout->addWidget(label);
+ layout->addLayout(buttonLayout);
- connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
- emit leaving();
- emit close();
- });
- connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close);
+ connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
+ emit leaving();
+ emit close();
+ });
+ connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close);
}
diff --git a/src/dialogs/LeaveRoom.h b/src/dialogs/LeaveRoom.h
index e9465579..edf88282 100644
--- a/src/dialogs/LeaveRoom.h
+++ b/src/dialogs/LeaveRoom.h
@@ -12,15 +12,15 @@ namespace dialogs {
class LeaveRoom : public QFrame
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit LeaveRoom(QWidget *parent = nullptr);
+ explicit LeaveRoom(QWidget *parent = nullptr);
signals:
- void leaving();
+ void leaving();
private:
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *confirmBtn_;
+ QPushButton *cancelBtn_;
};
} // dialogs
diff --git a/src/dialogs/Logout.cpp b/src/dialogs/Logout.cpp
index fdfc3338..d10e4cdf 100644
--- a/src/dialogs/Logout.cpp
+++ b/src/dialogs/Logout.cpp
@@ -15,40 +15,40 @@ using namespace dialogs;
Logout::Logout(QWidget *parent)
: QFrame(parent)
{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
-
- setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
-
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
-
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(0);
- buttonLayout->setMargin(0);
-
- confirmBtn_ = new QPushButton("Logout", this);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
- cancelBtn_->setDefault(true);
-
- buttonLayout->addStretch(1);
- buttonLayout->setSpacing(15);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
-
- auto label = new QLabel(tr("Logout. Are you sure?"), this);
-
- layout->addWidget(label);
- layout->addLayout(buttonLayout);
- layout->addStretch(1);
-
- connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
- emit loggingOut();
- emit close();
- });
- connect(cancelBtn_, &QPushButton::clicked, this, &Logout::close);
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+
+ setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(conf::modals::WIDGET_SPACING);
+ layout->setMargin(conf::modals::WIDGET_MARGIN);
+
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(0);
+ buttonLayout->setMargin(0);
+
+ confirmBtn_ = new QPushButton("Logout", this);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
+ cancelBtn_->setDefault(true);
+
+ buttonLayout->addStretch(1);
+ buttonLayout->setSpacing(15);
+ buttonLayout->addWidget(cancelBtn_);
+ buttonLayout->addWidget(confirmBtn_);
+
+ auto label = new QLabel(tr("Logout. Are you sure?"), this);
+
+ layout->addWidget(label);
+ layout->addLayout(buttonLayout);
+ layout->addStretch(1);
+
+ connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
+ emit loggingOut();
+ emit close();
+ });
+ connect(cancelBtn_, &QPushButton::clicked, this, &Logout::close);
}
diff --git a/src/dialogs/Logout.h b/src/dialogs/Logout.h
index 9d8d0f4b..7783c68f 100644
--- a/src/dialogs/Logout.h
+++ b/src/dialogs/Logout.h
@@ -13,15 +13,15 @@ namespace dialogs {
class Logout : public QFrame
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit Logout(QWidget *parent = nullptr);
+ explicit Logout(QWidget *parent = nullptr);
signals:
- void loggingOut();
+ void loggingOut();
private:
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *confirmBtn_;
+ QPushButton *cancelBtn_;
};
} // dialogs
diff --git a/src/dialogs/PreviewUploadOverlay.cpp b/src/dialogs/PreviewUploadOverlay.cpp
index 66fa1b37..e850c03b 100644
--- a/src/dialogs/PreviewUploadOverlay.cpp
+++ b/src/dialogs/PreviewUploadOverlay.cpp
@@ -29,188 +29,185 @@ PreviewUploadOverlay::PreviewUploadOverlay(QWidget *parent)
, upload_{tr("Upload"), this}
, cancel_{tr("Cancel"), this}
{
- auto hlayout = new QHBoxLayout;
- hlayout->addStretch(1);
- hlayout->addWidget(&cancel_);
- hlayout->addWidget(&upload_);
- hlayout->setMargin(0);
-
- auto vlayout = new QVBoxLayout{this};
- vlayout->addWidget(&titleLabel_);
- vlayout->addWidget(&infoLabel_);
- vlayout->addWidget(&fileName_);
- vlayout->addLayout(hlayout);
- vlayout->setSpacing(conf::modals::WIDGET_SPACING);
- vlayout->setMargin(conf::modals::WIDGET_MARGIN);
-
- upload_.setDefault(true);
- connect(&upload_, &QPushButton::clicked, [this]() {
- emit confirmUpload(data_, mediaType_, fileName_.text());
- close();
- });
-
- connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
- emit confirmUpload(data_, mediaType_, fileName_.text());
- close();
- });
-
- connect(&cancel_, &QPushButton::clicked, this, [this]() {
- emit aborted();
- close();
- });
+ auto hlayout = new QHBoxLayout;
+ hlayout->addStretch(1);
+ hlayout->addWidget(&cancel_);
+ hlayout->addWidget(&upload_);
+ hlayout->setMargin(0);
+
+ auto vlayout = new QVBoxLayout{this};
+ vlayout->addWidget(&titleLabel_);
+ vlayout->addWidget(&infoLabel_);
+ vlayout->addWidget(&fileName_);
+ vlayout->addLayout(hlayout);
+ vlayout->setSpacing(conf::modals::WIDGET_SPACING);
+ vlayout->setMargin(conf::modals::WIDGET_MARGIN);
+
+ upload_.setDefault(true);
+ connect(&upload_, &QPushButton::clicked, [this]() {
+ emit confirmUpload(data_, mediaType_, fileName_.text());
+ close();
+ });
+
+ connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
+ emit confirmUpload(data_, mediaType_, fileName_.text());
+ close();
+ });
+
+ connect(&cancel_, &QPushButton::clicked, this, [this]() {
+ emit aborted();
+ close();
+ });
}
void
PreviewUploadOverlay::init()
{
- QSize winsize;
- QPoint center;
-
- auto window = MainWindow::instance();
- if (window) {
- winsize = window->frameGeometry().size();
- center = window->frameGeometry().center();
- } else {
- nhlog::ui()->warn("unable to retrieve MainWindow's size");
- }
-
- fileName_.setText(QFileInfo{filePath_}.fileName());
-
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
-
- QFont font;
- font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
-
- titleLabel_.setFont(font);
- titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- titleLabel_.setAlignment(Qt::AlignCenter);
- infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
- fileName_.setAlignment(Qt::AlignCenter);
- upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-
- if (isImage_) {
- infoLabel_.setAlignment(Qt::AlignCenter);
-
- const auto maxWidth = winsize.width() * 0.8;
- const auto maxHeight = winsize.height() * 0.8;
-
- // Scale image preview to fit into the application window.
- infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
- move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
- } else {
- infoLabel_.setAlignment(Qt::AlignLeft);
- }
- infoLabel_.setScaledContents(false);
-
- show();
+ QSize winsize;
+ QPoint center;
+
+ auto window = MainWindow::instance();
+ if (window) {
+ winsize = window->frameGeometry().size();
+ center = window->frameGeometry().center();
+ } else {
+ nhlog::ui()->warn("unable to retrieve MainWindow's size");
+ }
+
+ fileName_.setText(QFileInfo{filePath_}.fileName());
+
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+
+ QFont font;
+ font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
+
+ titleLabel_.setFont(font);
+ titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ titleLabel_.setAlignment(Qt::AlignCenter);
+ infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ fileName_.setAlignment(Qt::AlignCenter);
+ upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ if (isImage_) {
+ infoLabel_.setAlignment(Qt::AlignCenter);
+
+ const auto maxWidth = winsize.width() * 0.8;
+ const auto maxHeight = winsize.height() * 0.8;
+
+ // Scale image preview to fit into the application window.
+ infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
+ move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
+ } else {
+ infoLabel_.setAlignment(Qt::AlignLeft);
+ }
+ infoLabel_.setScaledContents(false);
+
+ show();
}
void
PreviewUploadOverlay::setLabels(const QString &type, const QString &mime, uint64_t upload_size)
{
- if (mediaType_.split('/')[0] == "image") {
- if (!image_.loadFromData(data_)) {
- titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
- } else {
- titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
- }
- isImage_ = true;
+ if (mediaType_.split('/')[0] == "image") {
+ if (!image_.loadFromData(data_)) {
+ titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
} else {
- auto const info = QString{tr("Media type: %1\n"
- "Media size: %2\n")}
- .arg(mime)
- .arg(utils::humanReadableFileSize(upload_size));
-
- titleLabel_.setText(QString{tr(DEFAULT)}.arg("file"));
- infoLabel_.setText(info);
+ titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
}
+ isImage_ = true;
+ } else {
+ auto const info = QString{tr("Media type: %1\n"
+ "Media size: %2\n")}
+ .arg(mime)
+ .arg(utils::humanReadableFileSize(upload_size));
+
+ titleLabel_.setText(QString{tr(DEFAULT)}.arg("file"));
+ infoLabel_.setText(info);
+ }
}
void
PreviewUploadOverlay::setPreview(const QImage &src, const QString &mime)
{
- nhlog::ui()->info("Pasting image with size: {}x{}, format: {}",
- src.height(),
- src.width(),
- mime.toStdString());
-
- auto const &split = mime.split('/');
- auto const &type = split[1];
-
- QBuffer buffer(&data_);
- buffer.open(QIODevice::WriteOnly);
- if (src.save(&buffer, type.toStdString().c_str()))
- titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
- else
- titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
-
- mediaType_ = mime;
- filePath_ = "clipboard." + type;
- image_.convertFromImage(src);
- isImage_ = true;
+ nhlog::ui()->info(
+ "Pasting image with size: {}x{}, format: {}", src.height(), src.width(), mime.toStdString());
+
+ auto const &split = mime.split('/');
+ auto const &type = split[1];
+ QBuffer buffer(&data_);
+ buffer.open(QIODevice::WriteOnly);
+ if (src.save(&buffer, type.toStdString().c_str()))
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
- init();
+ else
+ titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
+
+ mediaType_ = mime;
+ filePath_ = "clipboard." + type;
+ image_.convertFromImage(src);
+ isImage_ = true;
+
+ titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
+ init();
}
void
PreviewUploadOverlay::setPreview(const QByteArray data, const QString &mime)
{
- auto const &split = mime.split('/');
- auto const &type = split[1];
+ auto const &split = mime.split('/');
+ auto const &type = split[1];
- data_ = data;
- mediaType_ = mime;
- filePath_ = "clipboard." + type;
- isImage_ = false;
+ data_ = data;
+ mediaType_ = mime;
+ filePath_ = "clipboard." + type;
+ isImage_ = false;
- setLabels(type, mime, data_.size());
- init();
+ setLabels(type, mime, data_.size());
+ init();
}
void
PreviewUploadOverlay::setPreview(const QString &path)
{
- QFile file{path};
-
- if (!file.open(QIODevice::ReadOnly)) {
- nhlog::ui()->warn("Failed to open file ({}): {}",
- path.toStdString(),
- file.errorString().toStdString());
- close();
- return;
- }
+ QFile file{path};
- QMimeDatabase db;
- auto mime = db.mimeTypeForFileNameAndData(path, &file);
+ if (!file.open(QIODevice::ReadOnly)) {
+ nhlog::ui()->warn(
+ "Failed to open file ({}): {}", path.toStdString(), file.errorString().toStdString());
+ close();
+ return;
+ }
- if ((data_ = file.readAll()).isEmpty()) {
- nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
- close();
- return;
- }
+ QMimeDatabase db;
+ auto mime = db.mimeTypeForFileNameAndData(path, &file);
- auto const &split = mime.name().split('/');
+ if ((data_ = file.readAll()).isEmpty()) {
+ nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
+ close();
+ return;
+ }
- mediaType_ = mime.name();
- filePath_ = file.fileName();
- isImage_ = false;
+ auto const &split = mime.name().split('/');
- setLabels(split[1], mime.name(), data_.size());
- init();
+ mediaType_ = mime.name();
+ filePath_ = file.fileName();
+ isImage_ = false;
+
+ setLabels(split[1], mime.name(), data_.size());
+ init();
}
void
PreviewUploadOverlay::keyPressEvent(QKeyEvent *event)
{
- if (event->matches(QKeySequence::Cancel)) {
- emit aborted();
- close();
- } else {
- QWidget::keyPressEvent(event);
- }
+ if (event->matches(QKeySequence::Cancel)) {
+ emit aborted();
+ close();
+ } else {
+ QWidget::keyPressEvent(event);
+ }
}
\ No newline at end of file
diff --git a/src/dialogs/PreviewUploadOverlay.h b/src/dialogs/PreviewUploadOverlay.h
index d23ea0ae..e9078069 100644
--- a/src/dialogs/PreviewUploadOverlay.h
+++ b/src/dialogs/PreviewUploadOverlay.h
@@ -18,35 +18,35 @@ namespace dialogs {
class PreviewUploadOverlay : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- PreviewUploadOverlay(QWidget *parent = nullptr);
+ PreviewUploadOverlay(QWidget *parent = nullptr);
- void setPreview(const QImage &src, const QString &mime);
- void setPreview(const QByteArray data, const QString &mime);
- void setPreview(const QString &path);
- void keyPressEvent(QKeyEvent *event);
+ void setPreview(const QImage &src, const QString &mime);
+ void setPreview(const QByteArray data, const QString &mime);
+ void setPreview(const QString &path);
+ void keyPressEvent(QKeyEvent *event);
signals:
- void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
- void aborted();
+ void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
+ void aborted();
private:
- void init();
- void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
+ void init();
+ void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
- bool isImage_;
- QPixmap image_;
+ bool isImage_;
+ QPixmap image_;
- QByteArray data_;
- QString filePath_;
- QString mediaType_;
+ QByteArray data_;
+ QString filePath_;
+ QString mediaType_;
- QLabel titleLabel_;
- QLabel infoLabel_;
- QLineEdit fileName_;
+ QLabel titleLabel_;
+ QLabel infoLabel_;
+ QLineEdit fileName_;
- QPushButton upload_;
- QPushButton cancel_;
+ QPushButton upload_;
+ QPushButton cancel_;
};
} // dialogs
diff --git a/src/dialogs/ReCaptcha.cpp b/src/dialogs/ReCaptcha.cpp
index c7b95f1a..0ae46bba 100644
--- a/src/dialogs/ReCaptcha.cpp
+++ b/src/dialogs/ReCaptcha.cpp
@@ -18,54 +18,54 @@ using namespace dialogs;
ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
: QWidget(parent)
{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
+ setAutoFillBackground(true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+ setAttribute(Qt::WA_DeleteOnClose, true);
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(conf::modals::WIDGET_SPACING);
+ layout->setMargin(conf::modals::WIDGET_MARGIN);
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(8);
- buttonLayout->setMargin(0);
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(8);
+ buttonLayout->setMargin(0);
- openCaptchaBtn_ = new QPushButton("Open reCAPTCHA", this);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
- confirmBtn_ = new QPushButton(tr("Confirm"), this);
- confirmBtn_->setDefault(true);
+ openCaptchaBtn_ = new QPushButton("Open reCAPTCHA", this);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
+ confirmBtn_ = new QPushButton(tr("Confirm"), this);
+ confirmBtn_->setDefault(true);
- buttonLayout->addStretch(1);
- buttonLayout->addWidget(openCaptchaBtn_);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(openCaptchaBtn_);
+ buttonLayout->addWidget(cancelBtn_);
+ buttonLayout->addWidget(confirmBtn_);
- QFont font;
- font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
+ QFont font;
+ font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
- auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
- label->setFont(font);
+ auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
+ label->setFont(font);
- layout->addWidget(label);
- layout->addLayout(buttonLayout);
+ layout->addWidget(label);
+ layout->addLayout(buttonLayout);
- connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
- const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
- "fallback/web?session=%3")
- .arg(QString::fromStdString(http::client()->server()))
- .arg(http::client()->port())
- .arg(session);
+ connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
+ const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
+ "fallback/web?session=%3")
+ .arg(QString::fromStdString(http::client()->server()))
+ .arg(http::client()->port())
+ .arg(session);
- QDesktopServices::openUrl(url);
- });
+ QDesktopServices::openUrl(url);
+ });
- connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
- emit confirmation();
- emit close();
- });
- connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
- emit cancel();
- emit close();
- });
+ connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
+ emit confirmation();
+ emit close();
+ });
+ connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
+ emit cancel();
+ emit close();
+ });
}
diff --git a/src/dialogs/ReCaptcha.h b/src/dialogs/ReCaptcha.h
index 0c9f7539..1e69de66 100644
--- a/src/dialogs/ReCaptcha.h
+++ b/src/dialogs/ReCaptcha.h
@@ -12,18 +12,18 @@ namespace dialogs {
class ReCaptcha : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- ReCaptcha(const QString &session, QWidget *parent = nullptr);
+ ReCaptcha(const QString &session, QWidget *parent = nullptr);
signals:
- void confirmation();
- void cancel();
+ void confirmation();
+ void cancel();
private:
- QPushButton *openCaptchaBtn_;
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *openCaptchaBtn_;
+ QPushButton *confirmBtn_;
+ QPushButton *cancelBtn_;
};
} // dialogs
diff --git a/src/emoji/EmojiModel.cpp b/src/emoji/EmojiModel.cpp
index 66e7aeda..07e6fdbd 100644
--- a/src/emoji/EmojiModel.cpp
+++ b/src/emoji/EmojiModel.cpp
@@ -14,63 +14,60 @@ using namespace emoji;
int
EmojiModel::categoryToIndex(int category)
{
- auto dist = std::distance(Provider::emoji.begin(),
- std::lower_bound(Provider::emoji.begin(),
- Provider::emoji.end(),
- static_cast<Emoji::Category>(category),
- [](const struct Emoji &e, Emoji::Category c) {
- return e.category < c;
- }));
+ auto dist = std::distance(
+ Provider::emoji.begin(),
+ std::lower_bound(Provider::emoji.begin(),
+ Provider::emoji.end(),
+ static_cast<Emoji::Category>(category),
+ [](const struct Emoji &e, Emoji::Category c) { return e.category < c; }));
- return static_cast<int>(dist);
+ return static_cast<int>(dist);
}
QHash<int, QByteArray>
EmojiModel::roleNames() const
{
- static QHash<int, QByteArray> roles;
+ static QHash<int, QByteArray> roles;
- if (roles.isEmpty()) {
- roles = QAbstractListModel::roleNames();
- roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
- roles[static_cast<int>(EmojiModel::Roles::ShortName)] =
- QByteArrayLiteral("shortName");
- roles[static_cast<int>(EmojiModel::Roles::Category)] =
- QByteArrayLiteral("category");
- roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
- }
+ if (roles.isEmpty()) {
+ roles = QAbstractListModel::roleNames();
+ roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
+ roles[static_cast<int>(EmojiModel::Roles::ShortName)] = QByteArrayLiteral("shortName");
+ roles[static_cast<int>(EmojiModel::Roles::Category)] = QByteArrayLiteral("category");
+ roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
+ }
- return roles;
+ return roles;
}
int
EmojiModel::rowCount(const QModelIndex &parent) const
{
- return parent == QModelIndex() ? Provider::emoji.count() : 0;
+ return parent == QModelIndex() ? Provider::emoji.count() : 0;
}
QVariant
EmojiModel::data(const QModelIndex &index, int role) const
{
- if (hasIndex(index.row(), index.column(), index.parent())) {
- switch (role) {
- case Qt::DisplayRole:
- case CompletionModel::CompletionRole:
- case static_cast<int>(EmojiModel::Roles::Unicode):
- return Provider::emoji[index.row()].unicode;
+ if (hasIndex(index.row(), index.column(), index.parent())) {
+ switch (role) {
+ case Qt::DisplayRole:
+ case CompletionModel::CompletionRole:
+ case static_cast<int>(EmojiModel::Roles::Unicode):
+ return Provider::emoji[index.row()].unicode;
- case Qt::ToolTipRole:
- case CompletionModel::SearchRole:
- case static_cast<int>(EmojiModel::Roles::ShortName):
- return Provider::emoji[index.row()].shortName;
+ case Qt::ToolTipRole:
+ case CompletionModel::SearchRole:
+ case static_cast<int>(EmojiModel::Roles::ShortName):
+ return Provider::emoji[index.row()].shortName;
- case static_cast<int>(EmojiModel::Roles::Category):
- return QVariant::fromValue(Provider::emoji[index.row()].category);
+ case static_cast<int>(EmojiModel::Roles::Category):
+ return QVariant::fromValue(Provider::emoji[index.row()].category);
- case static_cast<int>(EmojiModel::Roles::Emoji):
- return QVariant::fromValue(Provider::emoji[index.row()]);
- }
+ case static_cast<int>(EmojiModel::Roles::Emoji):
+ return QVariant::fromValue(Provider::emoji[index.row()]);
}
+ }
- return {};
+ return {};
}
diff --git a/src/emoji/EmojiModel.h b/src/emoji/EmojiModel.h
index 679563f1..882d3eb8 100644
--- a/src/emoji/EmojiModel.h
+++ b/src/emoji/EmojiModel.h
@@ -18,22 +18,22 @@ namespace emoji {
*/
class EmojiModel : public QAbstractListModel
{
- Q_OBJECT
+ Q_OBJECT
public:
- enum Roles
- {
- Unicode = Qt::UserRole, // unicode of emoji
- Category, // category of emoji
- ShortName, // shortext of the emoji
- Emoji, // Contains everything from the Emoji
- };
+ enum Roles
+ {
+ Unicode = Qt::UserRole, // unicode of emoji
+ Category, // category of emoji
+ ShortName, // shortext of the emoji
+ Emoji, // Contains everything from the Emoji
+ };
- using QAbstractListModel::QAbstractListModel;
+ using QAbstractListModel::QAbstractListModel;
- Q_INVOKABLE int categoryToIndex(int category);
+ Q_INVOKABLE int categoryToIndex(int category);
- QHash<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};
}
diff --git a/src/emoji/MacHelper.h b/src/emoji/MacHelper.h
index b3e2e631..ff49f9ba 100644
--- a/src/emoji/MacHelper.h
+++ b/src/emoji/MacHelper.h
@@ -9,6 +9,6 @@
class MacHelper
{
public:
- static void showEmojiWindow();
- static void initializeMenus();
+ static void showEmojiWindow();
+ static void initializeMenus();
};
diff --git a/src/emoji/Provider.h b/src/emoji/Provider.h
index 43c880a2..965329f9 100644
--- a/src/emoji/Provider.h
+++ b/src/emoji/Provider.h
@@ -16,37 +16,37 @@ Q_NAMESPACE
struct Emoji
{
- Q_GADGET
+ Q_GADGET
public:
- enum class Category
- {
- People,
- Nature,
- Food,
- Activity,
- Travel,
- Objects,
- Symbols,
- Flags,
- Search
- };
- Q_ENUM(Category)
-
- Q_PROPERTY(const QString &unicode MEMBER unicode)
- Q_PROPERTY(const QString &shortName MEMBER shortName)
- Q_PROPERTY(emoji::Emoji::Category category MEMBER category)
+ enum class Category
+ {
+ People,
+ Nature,
+ Food,
+ Activity,
+ Travel,
+ Objects,
+ Symbols,
+ Flags,
+ Search
+ };
+ Q_ENUM(Category)
+
+ Q_PROPERTY(const QString &unicode MEMBER unicode)
+ Q_PROPERTY(const QString &shortName MEMBER shortName)
+ Q_PROPERTY(emoji::Emoji::Category category MEMBER category)
public:
- QString unicode;
- QString shortName;
- Category category;
+ QString unicode;
+ QString shortName;
+ Category category;
};
class Provider
{
public:
- // all emoji for QML purposes
- static const QVector<Emoji> emoji;
+ // all emoji for QML purposes
+ static const QVector<Emoji> emoji;
};
} // namespace emoji
diff --git a/src/main.cpp b/src/main.cpp
index 09168e0c..cf7e29e6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -48,47 +48,47 @@ QQmlDebuggingEnabler enabler;
void
stacktraceHandler(int signum)
{
- std::signal(signum, SIG_DFL);
+ std::signal(signum, SIG_DFL);
- // boost::stacktrace::safe_dump_to("./nheko-backtrace.dump");
+ // boost::stacktrace::safe_dump_to("./nheko-backtrace.dump");
- // see
- // https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes/77336#77336
- void *array[50];
- size_t size;
+ // see
+ // https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes/77336#77336
+ void *array[50];
+ size_t size;
- // get void*'s for all entries on the stack
- size = backtrace(array, 50);
+ // get void*'s for all entries on the stack
+ size = backtrace(array, 50);
- // print out all the frames to stderr
- fprintf(stderr, "Error: signal %d:\n", signum);
- backtrace_symbols_fd(array, size, STDERR_FILENO);
+ // print out all the frames to stderr
+ fprintf(stderr, "Error: signal %d:\n", signum);
+ backtrace_symbols_fd(array, size, STDERR_FILENO);
- int file = ::open("/tmp/nheko-crash.dump",
- O_CREAT | O_WRONLY | O_TRUNC
+ int file = ::open("/tmp/nheko-crash.dump",
+ O_CREAT | O_WRONLY | O_TRUNC
#if defined(S_IWUSR) && defined(S_IRUSR)
- ,
- S_IWUSR | S_IRUSR
+ ,
+ S_IWUSR | S_IRUSR
#elif defined(S_IWRITE) && defined(S_IREAD)
- ,
- S_IWRITE | S_IREAD
+ ,
+ S_IWRITE | S_IREAD
#endif
- );
- if (file != -1) {
- constexpr char header[] = "Error: signal\n";
- [[maybe_unused]] auto ret = write(file, header, std::size(header) - 1);
- backtrace_symbols_fd(array, size, file);
- close(file);
- }
-
- std::raise(SIGABRT);
+ );
+ if (file != -1) {
+ constexpr char header[] = "Error: signal\n";
+ [[maybe_unused]] auto ret = write(file, header, std::size(header) - 1);
+ backtrace_symbols_fd(array, size, file);
+ close(file);
+ }
+
+ std::raise(SIGABRT);
}
void
registerSignalHandlers()
{
- std::signal(SIGSEGV, &stacktraceHandler);
- std::signal(SIGABRT, &stacktraceHandler);
+ std::signal(SIGSEGV, &stacktraceHandler);
+ std::signal(SIGABRT, &stacktraceHandler);
}
#else
@@ -103,203 +103,200 @@ registerSignalHandlers()
QPoint
screenCenter(int width, int height)
{
- // Deprecated in 5.13: QRect screenGeometry = QApplication::desktop()->screenGeometry();
- QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
+ // Deprecated in 5.13: QRect screenGeometry = QApplication::desktop()->screenGeometry();
+ QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
- int x = (screenGeometry.width() - width) / 2;
- int y = (screenGeometry.height() - height) / 2;
+ int x = (screenGeometry.width() - width) / 2;
+ int y = (screenGeometry.height() - height) / 2;
- return QPoint(x, y);
+ return QPoint(x, y);
}
void
createStandardDirectory(QStandardPaths::StandardLocation path)
{
- auto dir = QStandardPaths::writableLocation(path);
+ auto dir = QStandardPaths::writableLocation(path);
- if (!QDir().mkpath(dir)) {
- throw std::runtime_error(
- ("Unable to create state directory:" + dir).toStdString().c_str());
- }
+ if (!QDir().mkpath(dir)) {
+ throw std::runtime_error(("Unable to create state directory:" + dir).toStdString().c_str());
+ }
}
int
main(int argc, char *argv[])
{
- QCoreApplication::setApplicationName("nheko");
- QCoreApplication::setApplicationVersion(nheko::version);
- QCoreApplication::setOrganizationName("nheko");
- QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
- QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
- QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
-
- // this needs to be after setting the application name. Or how would we find our settings
- // file then?
+ QCoreApplication::setApplicationName("nheko");
+ QCoreApplication::setApplicationVersion(nheko::version);
+ QCoreApplication::setOrganizationName("nheko");
+ QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
+ // this needs to be after setting the application name. Or how would we find our settings
+ // file then?
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
- if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
- float factor = utils::scaleFactor();
+ if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
+ float factor = utils::scaleFactor();
- if (factor != -1)
- qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
- }
+ if (factor != -1)
+ qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
+ }
#endif
- // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
- // parsed before the SingleApplication userdata is set.
- QString userdata{""};
- QString matrixUri;
- for (int i = 1; i < argc; ++i) {
- QString arg{argv[i]};
- if (arg.startsWith("--profile=")) {
- arg.remove("--profile=");
- userdata = arg;
- } else if (arg.startsWith("--p=")) {
- arg.remove("-p=");
- userdata = arg;
- } else if (arg == "--profile" || arg == "-p") {
- if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
- // left to process as the name
- {
- ++i; // the next arg is the name, so increment
- userdata = QString{argv[i]};
- }
- } else if (arg.startsWith("matrix:")) {
- matrixUri = arg;
- }
- }
-
- SingleApplication app(argc,
- argv,
- true,
- SingleApplication::Mode::User |
- SingleApplication::Mode::ExcludeAppPath |
- SingleApplication::Mode::ExcludeAppVersion |
- SingleApplication::Mode::SecondaryNotification,
- 100,
- userdata);
-
- if (app.isSecondary()) {
- // open uri in main instance
- app.sendMessage(matrixUri.toUtf8());
- return 0;
- }
-
- QCommandLineParser parser;
- parser.addHelpOption();
- parser.addVersionOption();
- QCommandLineOption debugOption("debug", "Enable debug output");
- parser.addOption(debugOption);
-
- // This option is not actually parsed via Qt due to the need to parse it before the app
- // name is set. It only exists to keep Qt from complaining about the --profile/-p
- // option and thereby crashing the app.
- QCommandLineOption configName(
- QStringList() << "p"
- << "profile",
- QCoreApplication::tr("Create a unique profile, which allows you to log into several "
- "accounts at the same time and start multiple instances of nheko."),
- QCoreApplication::tr("profile"),
- QCoreApplication::tr("profile name"));
- parser.addOption(configName);
-
- parser.process(app);
-
- app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
-
- http::init();
-
- createStandardDirectory(QStandardPaths::CacheLocation);
- createStandardDirectory(QStandardPaths::AppDataLocation);
-
- registerSignalHandlers();
-
- if (parser.isSet(debugOption))
- nhlog::enable_debug_log_from_commandline = true;
-
- try {
- nhlog::init(QString("%1/nheko.log")
- .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .toStdString());
- } catch (const spdlog::spdlog_ex &ex) {
- std::cout << "Log initialization failed: " << ex.what() << std::endl;
- std::exit(1);
- }
-
- if (parser.isSet(configName))
- UserSettings::initialize(parser.value(configName));
- else
- UserSettings::initialize(std::nullopt);
-
- auto settings = UserSettings::instance().toWeakRef();
-
- QFont font;
- QString userFontFamily = settings.lock()->font();
- if (!userFontFamily.isEmpty() && userFontFamily != "default") {
- font.setFamily(userFontFamily);
+ // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
+ // parsed before the SingleApplication userdata is set.
+ QString userdata{""};
+ QString matrixUri;
+ for (int i = 1; i < argc; ++i) {
+ QString arg{argv[i]};
+ if (arg.startsWith("--profile=")) {
+ arg.remove("--profile=");
+ userdata = arg;
+ } else if (arg.startsWith("--p=")) {
+ arg.remove("-p=");
+ userdata = arg;
+ } else if (arg == "--profile" || arg == "-p") {
+ if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
+ // left to process as the name
+ {
+ ++i; // the next arg is the name, so increment
+ userdata = QString{argv[i]};
+ }
+ } else if (arg.startsWith("matrix:")) {
+ matrixUri = arg;
}
- font.setPointSizeF(settings.lock()->fontSize());
-
- app.setFont(font);
-
- QString lang = QLocale::system().name();
-
- QTranslator qtTranslator;
- qtTranslator.load(
- QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
- app.installTranslator(&qtTranslator);
-
- QTranslator appTranslator;
- appTranslator.load(QLocale(), "nheko", "_", ":/translations");
- app.installTranslator(&appTranslator);
-
- MainWindow w;
-
- // Move the MainWindow to the center
- w.move(screenCenter(w.width(), w.height()));
-
- if (!(settings.lock()->startInTray() && settings.lock()->tray()))
- w.show();
-
- QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
- w.saveCurrentWindowSize();
- if (http::client() != nullptr) {
- nhlog::net()->debug("shutting down all I/O threads & open connections");
- http::client()->close(true);
- nhlog::net()->debug("bye");
- }
- });
- QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
- w.show();
- w.raise();
- w.activateWindow();
- });
-
- QObject::connect(
- &app,
- &SingleApplication::receivedMessage,
- ChatPage::instance(),
- [&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
-
- QMetaObject::Connection uriConnection;
- if (app.isPrimary() && !matrixUri.isEmpty()) {
- uriConnection = QObject::connect(ChatPage::instance(),
- &ChatPage::contentLoaded,
- ChatPage::instance(),
- [&uriConnection, matrixUri]() {
- ChatPage::instance()->handleMatrixUri(
- matrixUri.toUtf8());
- QObject::disconnect(uriConnection);
- });
+ }
+
+ SingleApplication app(argc,
+ argv,
+ true,
+ SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppPath |
+ SingleApplication::Mode::ExcludeAppVersion |
+ SingleApplication::Mode::SecondaryNotification,
+ 100,
+ userdata);
+
+ if (app.isSecondary()) {
+ // open uri in main instance
+ app.sendMessage(matrixUri.toUtf8());
+ return 0;
+ }
+
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.addVersionOption();
+ QCommandLineOption debugOption("debug", "Enable debug output");
+ parser.addOption(debugOption);
+
+ // This option is not actually parsed via Qt due to the need to parse it before the app
+ // name is set. It only exists to keep Qt from complaining about the --profile/-p
+ // option and thereby crashing the app.
+ QCommandLineOption configName(
+ QStringList() << "p"
+ << "profile",
+ QCoreApplication::tr("Create a unique profile, which allows you to log into several "
+ "accounts at the same time and start multiple instances of nheko."),
+ QCoreApplication::tr("profile"),
+ QCoreApplication::tr("profile name"));
+ parser.addOption(configName);
+
+ parser.process(app);
+
+ app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
+
+ http::init();
+
+ createStandardDirectory(QStandardPaths::CacheLocation);
+ createStandardDirectory(QStandardPaths::AppDataLocation);
+
+ registerSignalHandlers();
+
+ if (parser.isSet(debugOption))
+ nhlog::enable_debug_log_from_commandline = true;
+
+ try {
+ nhlog::init(QString("%1/nheko.log")
+ .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
+ .toStdString());
+ } catch (const spdlog::spdlog_ex &ex) {
+ std::cout << "Log initialization failed: " << ex.what() << std::endl;
+ std::exit(1);
+ }
+
+ if (parser.isSet(configName))
+ UserSettings::initialize(parser.value(configName));
+ else
+ UserSettings::initialize(std::nullopt);
+
+ auto settings = UserSettings::instance().toWeakRef();
+
+ QFont font;
+ QString userFontFamily = settings.lock()->font();
+ if (!userFontFamily.isEmpty() && userFontFamily != "default") {
+ font.setFamily(userFontFamily);
+ }
+ font.setPointSizeF(settings.lock()->fontSize());
+
+ app.setFont(font);
+
+ QString lang = QLocale::system().name();
+
+ QTranslator qtTranslator;
+ qtTranslator.load(QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+ app.installTranslator(&qtTranslator);
+
+ QTranslator appTranslator;
+ appTranslator.load(QLocale(), "nheko", "_", ":/translations");
+ app.installTranslator(&appTranslator);
+
+ MainWindow w;
+
+ // Move the MainWindow to the center
+ w.move(screenCenter(w.width(), w.height()));
+
+ if (!(settings.lock()->startInTray() && settings.lock()->tray()))
+ w.show();
+
+ QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
+ w.saveCurrentWindowSize();
+ if (http::client() != nullptr) {
+ nhlog::net()->debug("shutting down all I/O threads & open connections");
+ http::client()->close(true);
+ nhlog::net()->debug("bye");
}
- QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
+ });
+ QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
+ w.show();
+ w.raise();
+ w.activateWindow();
+ });
+
+ QObject::connect(
+ &app,
+ &SingleApplication::receivedMessage,
+ ChatPage::instance(),
+ [&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
+
+ QMetaObject::Connection uriConnection;
+ if (app.isPrimary() && !matrixUri.isEmpty()) {
+ uriConnection =
+ QObject::connect(ChatPage::instance(),
+ &ChatPage::contentLoaded,
+ ChatPage::instance(),
+ [&uriConnection, matrixUri]() {
+ ChatPage::instance()->handleMatrixUri(matrixUri.toUtf8());
+ QObject::disconnect(uriConnection);
+ });
+ }
+ QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
#if defined(Q_OS_MAC)
- // Temporary solution for the emoji picker until
- // nheko has a proper menu bar with more functionality.
- MacHelper::initializeMenus();
+ // Temporary solution for the emoji picker until
+ // nheko has a proper menu bar with more functionality.
+ MacHelper::initializeMenus();
#endif
- nhlog::ui()->info("starting nheko {}", nheko::version);
+ nhlog::ui()->info("starting nheko {}", nheko::version);
- return app.exec();
+ return app.exec();
}
diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp
index be580b08..f24a48ff 100644
--- a/src/notifications/Manager.cpp
+++ b/src/notifications/Manager.cpp
@@ -11,30 +11,30 @@
QString
NotificationsManager::getMessageTemplate(const mtx::responses::Notification ¬ification)
{
- const auto sender =
- cache::displayName(QString::fromStdString(notification.room_id),
- QString::fromStdString(mtx::accessors::sender(notification.event)));
+ const auto sender =
+ cache::displayName(QString::fromStdString(notification.room_id),
+ QString::fromStdString(mtx::accessors::sender(notification.event)));
- // TODO: decrypt this message if the decryption setting is on in the UserSettings
- if (auto msg = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- ¬ification.event);
- msg != nullptr) {
- return tr("%1 sent an encrypted message").arg(sender);
- }
+ // TODO: decrypt this message if the decryption setting is on in the UserSettings
+ if (auto msg = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ ¬ification.event);
+ msg != nullptr) {
+ return tr("%1 sent an encrypted message").arg(sender);
+ }
- if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) {
- return tr("* %1 %2",
- "Format an emote message in a notification, %1 is the sender, %2 the "
- "message")
- .arg(sender);
- } else if (utils::isReply(notification.event)) {
- return tr("%1 replied: %2",
- "Format a reply in a notification. %1 is the sender, %2 the message")
- .arg(sender);
- } else {
- return tr("%1: %2",
- "Format a normal message in a notification. %1 is the sender, %2 the "
- "message")
- .arg(sender);
- }
+ if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) {
+ return tr("* %1 %2",
+ "Format an emote message in a notification, %1 is the sender, %2 the "
+ "message")
+ .arg(sender);
+ } else if (utils::isReply(notification.event)) {
+ return tr("%1 replied: %2",
+ "Format a reply in a notification. %1 is the sender, %2 the message")
+ .arg(sender);
+ } else {
+ return tr("%1: %2",
+ "Format a normal message in a notification. %1 is the sender, %2 the "
+ "message")
+ .arg(sender);
+ }
}
diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h
index 416530e0..4e24dd1b 100644
--- a/src/notifications/Manager.h
+++ b/src/notifications/Manager.h
@@ -22,83 +22,83 @@
struct roomEventId
{
- QString roomId;
- QString eventId;
+ QString roomId;
+ QString eventId;
};
inline bool
operator==(const roomEventId &a, const roomEventId &b)
{
- return a.roomId == b.roomId && a.eventId == b.eventId;
+ return a.roomId == b.roomId && a.eventId == b.eventId;
}
class NotificationsManager : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
- NotificationsManager(QObject *parent = nullptr);
+ NotificationsManager(QObject *parent = nullptr);
- void postNotification(const mtx::responses::Notification ¬ification, const QImage &icon);
+ void postNotification(const mtx::responses::Notification ¬ification, const QImage &icon);
signals:
- void notificationClicked(const QString roomId, const QString eventId);
- void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
- void systemPostNotificationCb(const QString &room_id,
- const QString &event_id,
- const QString &roomName,
- const QString &text,
- const QImage &icon);
+ void notificationClicked(const QString roomId, const QString eventId);
+ void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
+ void systemPostNotificationCb(const QString &room_id,
+ const QString &event_id,
+ const QString &roomName,
+ const QString &text,
+ const QImage &icon);
public slots:
- void removeNotification(const QString &roomId, const QString &eventId);
+ void removeNotification(const QString &roomId, const QString &eventId);
#if defined(NHEKO_DBUS_SYS)
public:
- void closeNotifications(QString roomId);
+ void closeNotifications(QString roomId);
private:
- QDBusInterface dbus;
+ QDBusInterface dbus;
- void systemPostNotification(const QString &room_id,
- const QString &event_id,
- const QString &roomName,
- const QString &text,
- const QImage &icon);
- void closeNotification(uint id);
+ void systemPostNotification(const QString &room_id,
+ const QString &event_id,
+ const QString &roomName,
+ const QString &text,
+ const QImage &icon);
+ void closeNotification(uint id);
- // notification ID to (room ID, event ID)
- QMap<uint, roomEventId> notificationIds;
+ // notification ID to (room ID, event ID)
+ QMap<uint, roomEventId> notificationIds;
- const bool hasMarkup_;
- const bool hasImages_;
+ const bool hasMarkup_;
+ const bool hasImages_;
#endif
#if defined(Q_OS_MACOS)
private:
- // Objective-C(++) doesn't like to do lots of regular C++, so the actual notification
- // posting is split out
- void objCxxPostNotification(const QString &title,
- const QString &subtitle,
- const QString &informativeText,
- const QImage &bodyImage);
+ // Objective-C(++) doesn't like to do lots of regular C++, so the actual notification
+ // posting is split out
+ void objCxxPostNotification(const QString &title,
+ const QString &subtitle,
+ const QString &informativeText,
+ const QImage &bodyImage);
#endif
#if defined(Q_OS_WINDOWS)
private:
- void systemPostNotification(const QString &line1,
- const QString &line2,
- const QString &iconPath);
+ void systemPostNotification(const QString &line1,
+ const QString &line2,
+ const QString &iconPath);
#endif
- // these slots are platform specific (D-Bus only)
- // but Qt slot declarations can not be inside an ifdef!
+ // these slots are platform specific (D-Bus only)
+ // but Qt slot declarations can not be inside an ifdef!
private slots:
- void actionInvoked(uint id, QString action);
- void notificationClosed(uint id, uint reason);
- void notificationReplied(uint id, QString reply);
+ void actionInvoked(uint id, QString action);
+ void notificationClosed(uint id, uint reason);
+ void notificationReplied(uint id, QString reply);
private:
- QString getMessageTemplate(const mtx::responses::Notification ¬ification);
+ QString getMessageTemplate(const mtx::responses::Notification ¬ification);
};
#if defined(NHEKO_DBUS_SYS)
diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp
index 2809de87..758cb615 100644
--- a/src/notifications/ManagerLinux.cpp
+++ b/src/notifications/ManagerLinux.cpp
@@ -34,109 +34,100 @@ NotificationsManager::NotificationsManager(QObject *parent)
QDBusConnection::sessionBus(),
this)
, hasMarkup_{std::invoke([this]() -> bool {
- for (auto x : dbus.call("GetCapabilities").arguments())
- if (x.toStringList().contains("body-markup"))
- return true;
- return false;
+ for (auto x : dbus.call("GetCapabilities").arguments())
+ if (x.toStringList().contains("body-markup"))
+ return true;
+ return false;
})}
, hasImages_{std::invoke([this]() -> bool {
- for (auto x : dbus.call("GetCapabilities").arguments())
- if (x.toStringList().contains("body-images"))
- return true;
- return false;
+ for (auto x : dbus.call("GetCapabilities").arguments())
+ if (x.toStringList().contains("body-images"))
+ return true;
+ return false;
})}
{
- qDBusRegisterMetaType<QImage>();
+ qDBusRegisterMetaType<QImage>();
- QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
- "/org/freedesktop/Notifications",
- "org.freedesktop.Notifications",
- "ActionInvoked",
- this,
- SLOT(actionInvoked(uint, QString)));
- QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
- "/org/freedesktop/Notifications",
- "org.freedesktop.Notifications",
- "NotificationClosed",
- this,
- SLOT(notificationClosed(uint, uint)));
- QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
- "/org/freedesktop/Notifications",
- "org.freedesktop.Notifications",
- "NotificationReplied",
- this,
- SLOT(notificationReplied(uint, QString)));
+ QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "ActionInvoked",
+ this,
+ SLOT(actionInvoked(uint, QString)));
+ QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "NotificationClosed",
+ this,
+ SLOT(notificationClosed(uint, uint)));
+ QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "NotificationReplied",
+ this,
+ SLOT(notificationReplied(uint, QString)));
- connect(this,
- &NotificationsManager::systemPostNotificationCb,
- this,
- &NotificationsManager::systemPostNotification,
- Qt::QueuedConnection);
+ connect(this,
+ &NotificationsManager::systemPostNotificationCb,
+ this,
+ &NotificationsManager::systemPostNotification,
+ Qt::QueuedConnection);
}
void
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
const QImage &icon)
{
- const auto room_id = QString::fromStdString(notification.room_id);
- const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
- const auto room_name =
- QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
+ const auto room_id = QString::fromStdString(notification.room_id);
+ const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
+ const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
- auto postNotif = [this, room_id, event_id, room_name, icon](QString text) {
- emit systemPostNotificationCb(room_id, event_id, room_name, text, icon);
- };
-
- QString template_ = getMessageTemplate(notification);
- // TODO: decrypt this message if the decryption setting is on in the UserSettings
- if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- notification.event)) {
- postNotif(template_);
- return;
- }
+ auto postNotif = [this, room_id, event_id, room_name, icon](QString text) {
+ emit systemPostNotificationCb(room_id, event_id, room_name, text, icon);
+ };
- if (hasMarkup_) {
- if (hasImages_ && mtx::accessors::msg_type(notification.event) ==
- mtx::events::MessageType::Image) {
- MxcImageProvider::download(
- QString::fromStdString(mtx::accessors::url(notification.event))
- .remove("mxc://"),
- QSize(200, 80),
- [postNotif, notification, template_](
- QString, QSize, QImage, QString imgPath) {
- if (imgPath.isEmpty())
- postNotif(template_
- .arg(utils::stripReplyFallbacks(
- notification.event, {}, {})
- .quoted_formatted_body)
- .replace("<em>", "<i>")
- .replace("</em>", "</i>")
- .replace("<strong>", "<b>")
- .replace("</strong>", "</b>"));
- else
- postNotif(template_.arg(
- QStringLiteral("<br><img src=\"file:///") % imgPath %
- "\" alt=\"" %
- mtx::accessors::formattedBodyWithFallback(
- notification.event) %
- "\">"));
- });
- return;
- }
+ QString template_ = getMessageTemplate(notification);
+ // TODO: decrypt this message if the decryption setting is on in the UserSettings
+ if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ notification.event)) {
+ postNotif(template_);
+ return;
+ }
- postNotif(
- template_
- .arg(
- utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body)
- .replace("<em>", "<i>")
- .replace("</em>", "</i>")
- .replace("<strong>", "<b>")
- .replace("</strong>", "</b>"));
- return;
+ if (hasMarkup_) {
+ if (hasImages_ &&
+ mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) {
+ MxcImageProvider::download(
+ QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
+ QSize(200, 80),
+ [postNotif, notification, template_](QString, QSize, QImage, QString imgPath) {
+ if (imgPath.isEmpty())
+ postNotif(template_
+ .arg(utils::stripReplyFallbacks(notification.event, {}, {})
+ .quoted_formatted_body)
+ .replace("<em>", "<i>")
+ .replace("</em>", "</i>")
+ .replace("<strong>", "<b>")
+ .replace("</strong>", "</b>"));
+ else
+ postNotif(template_.arg(
+ QStringLiteral("<br><img src=\"file:///") % imgPath % "\" alt=\"" %
+ mtx::accessors::formattedBodyWithFallback(notification.event) % "\">"));
+ });
+ return;
}
postNotif(
- template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
+ template_
+ .arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body)
+ .replace("<em>", "<i>")
+ .replace("</em>", "</i>")
+ .replace("<strong>", "<b>")
+ .replace("</strong>", "</b>"));
+ return;
+ }
+
+ postNotif(template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
}
/**
@@ -152,99 +143,99 @@ NotificationsManager::systemPostNotification(const QString &room_id,
const QString &text,
const QImage &icon)
{
- QVariantMap hints;
- hints["image-data"] = icon;
- hints["sound-name"] = "message-new-instant";
- QList<QVariant> argumentList;
- argumentList << "nheko"; // app_name
- argumentList << (uint)0; // replace_id
- argumentList << ""; // app_icon
- argumentList << roomName; // summary
- argumentList << text; // body
+ QVariantMap hints;
+ hints["image-data"] = icon;
+ hints["sound-name"] = "message-new-instant";
+ QList<QVariant> argumentList;
+ argumentList << "nheko"; // app_name
+ argumentList << (uint)0; // replace_id
+ argumentList << ""; // app_icon
+ argumentList << roomName; // summary
+ argumentList << text; // body
- // The list of actions has always the action name and then a localized version of that
- // action. Currently we just use an empty string for that.
- // TODO(Nico): Look into what to actually put there.
- argumentList << (QStringList("default") << ""
- << "inline-reply"
- << ""); // actions
- argumentList << hints; // hints
- argumentList << (int)-1; // timeout in ms
+ // The list of actions has always the action name and then a localized version of that
+ // action. Currently we just use an empty string for that.
+ // TODO(Nico): Look into what to actually put there.
+ argumentList << (QStringList("default") << ""
+ << "inline-reply"
+ << ""); // actions
+ argumentList << hints; // hints
+ argumentList << (int)-1; // timeout in ms
- QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
- auto watcher = new QDBusPendingCallWatcher{call, this};
- connect(
- watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
- if (watcher->reply().type() == QDBusMessage::ErrorMessage)
- qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
- else
- notificationIds[watcher->reply().arguments().first().toUInt()] =
- roomEventId{room_id, event_id};
- watcher->deleteLater();
- });
+ QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
+ auto watcher = new QDBusPendingCallWatcher{call, this};
+ connect(
+ watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
+ if (watcher->reply().type() == QDBusMessage::ErrorMessage)
+ qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
+ else
+ notificationIds[watcher->reply().arguments().first().toUInt()] =
+ roomEventId{room_id, event_id};
+ watcher->deleteLater();
+ });
}
void
NotificationsManager::closeNotification(uint id)
{
- auto call = dbus.asyncCall("CloseNotification", (uint)id); // replace_id
- auto watcher = new QDBusPendingCallWatcher{call, this};
- connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() {
- if (watcher->reply().type() == QDBusMessage::ErrorMessage) {
- qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
- };
- watcher->deleteLater();
- });
+ auto call = dbus.asyncCall("CloseNotification", (uint)id); // replace_id
+ auto watcher = new QDBusPendingCallWatcher{call, this};
+ connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() {
+ if (watcher->reply().type() == QDBusMessage::ErrorMessage) {
+ qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
+ };
+ watcher->deleteLater();
+ });
}
void
NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
{
- roomEventId reId = {roomId, eventId};
- for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
- if (elem.value().roomId != roomId)
- continue;
+ roomEventId reId = {roomId, eventId};
+ for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
+ if (elem.value().roomId != roomId)
+ continue;
- // close all notifications matching the eventId or having a lower
- // notificationId
- // This relies on the notificationId not wrapping around. This allows for
- // approximately 2,147,483,647 notifications, so it is a bit unlikely.
- // Otherwise we would need to store a 64bit counter instead.
- closeNotification(elem.key());
+ // close all notifications matching the eventId or having a lower
+ // notificationId
+ // This relies on the notificationId not wrapping around. This allows for
+ // approximately 2,147,483,647 notifications, so it is a bit unlikely.
+ // Otherwise we would need to store a 64bit counter instead.
+ closeNotification(elem.key());
- // FIXME: compare index of event id of the read receipt and the notification instead
- // of just the id to prevent read receipts of events without notification clearing
- // all notifications in that room!
- if (elem.value() == reId)
- break;
- }
+ // FIXME: compare index of event id of the read receipt and the notification instead
+ // of just the id to prevent read receipts of events without notification clearing
+ // all notifications in that room!
+ if (elem.value() == reId)
+ break;
+ }
}
void
NotificationsManager::actionInvoked(uint id, QString action)
{
- if (notificationIds.contains(id)) {
- roomEventId idEntry = notificationIds[id];
- if (action == "default") {
- emit notificationClicked(idEntry.roomId, idEntry.eventId);
- }
+ if (notificationIds.contains(id)) {
+ roomEventId idEntry = notificationIds[id];
+ if (action == "default") {
+ emit notificationClicked(idEntry.roomId, idEntry.eventId);
}
+ }
}
void
NotificationsManager::notificationReplied(uint id, QString reply)
{
- if (notificationIds.contains(id)) {
- roomEventId idEntry = notificationIds[id];
- emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply);
- }
+ if (notificationIds.contains(id)) {
+ roomEventId idEntry = notificationIds[id];
+ emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply);
+ }
}
void
NotificationsManager::notificationClosed(uint id, uint reason)
{
- Q_UNUSED(reason);
- notificationIds.remove(id);
+ Q_UNUSED(reason);
+ notificationIds.remove(id);
}
/**
@@ -259,52 +250,52 @@ NotificationsManager::notificationClosed(uint id, uint reason)
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image)
{
- if (image.isNull()) {
- arg.beginStructure();
- arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
- arg.endStructure();
- return arg;
- }
+ if (image.isNull()) {
+ arg.beginStructure();
+ arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
+ arg.endStructure();
+ return arg;
+ }
- QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
- scaled = scaled.convertToFormat(QImage::Format_ARGB32);
+ QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
+ scaled = scaled.convertToFormat(QImage::Format_ARGB32);
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
- // ABGR -> ARGB
- QImage i = scaled.rgbSwapped();
+ // ABGR -> ARGB
+ QImage i = scaled.rgbSwapped();
#else
- // ABGR -> GBAR
- QImage i(scaled.size(), scaled.format());
- for (int y = 0; y < i.height(); ++y) {
- QRgb *p = (QRgb *)scaled.scanLine(y);
- QRgb *q = (QRgb *)i.scanLine(y);
- QRgb *end = p + scaled.width();
- while (p < end) {
- *q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
- p++;
- q++;
- }
+ // ABGR -> GBAR
+ QImage i(scaled.size(), scaled.format());
+ for (int y = 0; y < i.height(); ++y) {
+ QRgb *p = (QRgb *)scaled.scanLine(y);
+ QRgb *q = (QRgb *)i.scanLine(y);
+ QRgb *end = p + scaled.width();
+ while (p < end) {
+ *q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
+ p++;
+ q++;
}
+ }
#endif
- arg.beginStructure();
- arg << i.width();
- arg << i.height();
- arg << i.bytesPerLine();
- arg << i.hasAlphaChannel();
- int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
- arg << i.depth() / channels;
- arg << channels;
- arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
- arg.endStructure();
+ arg.beginStructure();
+ arg << i.width();
+ arg << i.height();
+ arg << i.bytesPerLine();
+ arg << i.hasAlphaChannel();
+ int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
+ arg << i.depth() / channels;
+ arg << channels;
+ arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
+ arg.endStructure();
- return arg;
+ return arg;
}
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &)
{
- // This is needed to link but shouldn't be called.
- Q_ASSERT(0);
- return arg;
+ // This is needed to link but shouldn't be called.
+ Q_ASSERT(0);
+ return arg;
}
diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp
index 8e36985c..f69cec2c 100644
--- a/src/notifications/ManagerMac.cpp
+++ b/src/notifications/ManagerMac.cpp
@@ -19,48 +19,42 @@
static QString
formatNotification(const mtx::responses::Notification ¬ification)
{
- return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body;
+ return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body;
}
void
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
const QImage &icon)
{
- Q_UNUSED(icon)
-
- const auto room_name =
- QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
- const auto sender =
- cache::displayName(QString::fromStdString(notification.room_id),
- QString::fromStdString(mtx::accessors::sender(notification.event)));
-
- const auto isEncrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- ¬ification.event) != nullptr;
- const auto isReply = utils::isReply(notification.event);
- if (isEncrypted) {
- // TODO: decrypt this message if the decryption setting is on in the UserSettings
- const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message")
- : tr("%1 sent an encrypted message"))
- .arg(sender);
- objCxxPostNotification(room_name, messageInfo, "", QImage());
- } else {
- const QString messageInfo =
- (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
- if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)
- MxcImageProvider::download(
- QString::fromStdString(mtx::accessors::url(notification.event))
- .remove("mxc://"),
- QSize(200, 80),
- [this, notification, room_name, messageInfo](
- QString, QSize, QImage image, QString) {
- objCxxPostNotification(room_name,
- messageInfo,
- formatNotification(notification),
- image);
- });
- else
- objCxxPostNotification(
- room_name, messageInfo, formatNotification(notification), QImage());
- }
+ Q_UNUSED(icon)
+
+ const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
+ const auto sender =
+ cache::displayName(QString::fromStdString(notification.room_id),
+ QString::fromStdString(mtx::accessors::sender(notification.event)));
+
+ const auto isEncrypted = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ ¬ification.event) != nullptr;
+ const auto isReply = utils::isReply(notification.event);
+ if (isEncrypted) {
+ // TODO: decrypt this message if the decryption setting is on in the UserSettings
+ const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message")
+ : tr("%1 sent an encrypted message"))
+ .arg(sender);
+ objCxxPostNotification(room_name, messageInfo, "", QImage());
+ } else {
+ const QString messageInfo =
+ (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
+ if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)
+ MxcImageProvider::download(
+ QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
+ QSize(200, 80),
+ [this, notification, room_name, messageInfo](QString, QSize, QImage image, QString) {
+ objCxxPostNotification(
+ room_name, messageInfo, formatNotification(notification), image);
+ });
+ else
+ objCxxPostNotification(
+ room_name, messageInfo, formatNotification(notification), QImage());
+ }
}
diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp
index fe7830a7..4376e4d8 100644
--- a/src/notifications/ManagerWin.cpp
+++ b/src/notifications/ManagerWin.cpp
@@ -20,10 +20,10 @@ using namespace WinToastLib;
class CustomHandler : public IWinToastHandler
{
public:
- void toastActivated() const {}
- void toastActivated(int) const {}
- void toastFailed() const { std::wcout << L"Error showing current toast" << std::endl; }
- void toastDismissed(WinToastDismissalReason) const {}
+ void toastActivated() const {}
+ void toastActivated(int) const {}
+ void toastFailed() const { std::wcout << L"Error showing current toast" << std::endl; }
+ void toastDismissed(WinToastDismissalReason) const {}
};
namespace {
@@ -32,12 +32,12 @@ bool isInitialized = false;
void
init()
{
- isInitialized = true;
+ isInitialized = true;
- WinToast::instance()->setAppName(L"Nheko");
- WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"nheko", L"nheko"));
- if (!WinToast::instance()->initialize())
- std::wcout << "Your system is not compatible with toast notifications\n";
+ WinToast::instance()->setAppName(L"Nheko");
+ WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"nheko", L"nheko"));
+ if (!WinToast::instance()->initialize())
+ std::wcout << "Your system is not compatible with toast notifications\n";
}
}
@@ -49,41 +49,37 @@ void
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
const QImage &icon)
{
- const auto room_name =
- QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
- const auto sender =
- cache::displayName(QString::fromStdString(notification.room_id),
- QString::fromStdString(mtx::accessors::sender(notification.event)));
-
- const auto isEncrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- ¬ification.event) != nullptr;
- const auto isReply = utils::isReply(notification.event);
-
- auto formatNotification = [this, notification, sender] {
- const auto template_ = getMessageTemplate(notification);
- if (std::holds_alternative<
- mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- notification.event)) {
- return template_;
- }
-
- return template_.arg(
- utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body);
- };
-
- const auto line1 =
- (room_name == sender) ? sender : QString("%1 - %2").arg(sender).arg(room_name);
- const auto line2 = (isEncrypted ? (isReply ? tr("%1 replied with an encrypted message")
- : tr("%1 sent an encrypted message"))
- : formatNotification());
-
- auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
- room_name + "-room-avatar.png";
- if (!icon.save(iconPath))
- iconPath.clear();
-
- systemPostNotification(line1, line2, iconPath);
+ const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
+ const auto sender =
+ cache::displayName(QString::fromStdString(notification.room_id),
+ QString::fromStdString(mtx::accessors::sender(notification.event)));
+
+ const auto isEncrypted = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ ¬ification.event) != nullptr;
+ const auto isReply = utils::isReply(notification.event);
+
+ auto formatNotification = [this, notification, sender] {
+ const auto template_ = getMessageTemplate(notification);
+ if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ notification.event)) {
+ return template_;
+ }
+
+ return template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body);
+ };
+
+ const auto line1 =
+ (room_name == sender) ? sender : QString("%1 - %2").arg(sender).arg(room_name);
+ const auto line2 = (isEncrypted ? (isReply ? tr("%1 replied with an encrypted message")
+ : tr("%1 sent an encrypted message"))
+ : formatNotification());
+
+ auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + room_name +
+ "-room-avatar.png";
+ if (!icon.save(iconPath))
+ iconPath.clear();
+
+ systemPostNotification(line1, line2, iconPath);
}
void
@@ -91,17 +87,17 @@ NotificationsManager::systemPostNotification(const QString &line1,
const QString &line2,
const QString &iconPath)
{
- if (!isInitialized)
- init();
+ if (!isInitialized)
+ init();
- auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
- templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
- templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
+ auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
+ templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
+ templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
- if (!iconPath.isNull())
- templ.setImagePath(iconPath.toStdWString());
+ if (!iconPath.isNull())
+ templ.setImagePath(iconPath.toStdWString());
- WinToast::instance()->showToast(templ, new CustomHandler());
+ WinToast::instance()->showToast(templ, new CustomHandler());
}
void NotificationsManager::actionInvoked(uint, QString) {}
diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp
index 97bfa76d..77bed387 100644
--- a/src/timeline/CommunitiesModel.cpp
+++ b/src/timeline/CommunitiesModel.cpp
@@ -16,231 +16,230 @@ CommunitiesModel::CommunitiesModel(QObject *parent)
QHash<int, QByteArray>
CommunitiesModel::roleNames() const
{
- return {
- {AvatarUrl, "avatarUrl"},
- {DisplayName, "displayName"},
- {Tooltip, "tooltip"},
- {ChildrenHidden, "childrenHidden"},
- {Hidden, "hidden"},
- {Id, "id"},
- };
+ return {
+ {AvatarUrl, "avatarUrl"},
+ {DisplayName, "displayName"},
+ {Tooltip, "tooltip"},
+ {ChildrenHidden, "childrenHidden"},
+ {Hidden, "hidden"},
+ {Id, "id"},
+ };
}
QVariant
CommunitiesModel::data(const QModelIndex &index, int role) const
{
- if (index.row() == 0) {
- switch (role) {
- case CommunitiesModel::Roles::AvatarUrl:
- return QString(":/icons/icons/ui/world.png");
- case CommunitiesModel::Roles::DisplayName:
- return tr("All rooms");
- case CommunitiesModel::Roles::Tooltip:
- return tr("Shows all rooms without filtering.");
- case CommunitiesModel::Roles::ChildrenHidden:
- return false;
- case CommunitiesModel::Roles::Hidden:
- return false;
- case CommunitiesModel::Roles::Id:
- return "";
- }
- } else if (index.row() - 1 < spaceOrder_.size()) {
- auto id = spaceOrder_.at(index.row() - 1);
- switch (role) {
- case CommunitiesModel::Roles::AvatarUrl:
- return QString::fromStdString(spaces_.at(id).avatar_url);
- case CommunitiesModel::Roles::DisplayName:
- case CommunitiesModel::Roles::Tooltip:
- return QString::fromStdString(spaces_.at(id).name);
- case CommunitiesModel::Roles::ChildrenHidden:
- return true;
- case CommunitiesModel::Roles::Hidden:
- return hiddentTagIds_.contains("space:" + id);
- case CommunitiesModel::Roles::Id:
- return "space:" + id;
- }
- } else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
- auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
- if (tag == "m.favourite") {
- switch (role) {
- case CommunitiesModel::Roles::AvatarUrl:
- return QString(":/icons/icons/ui/star.png");
- case CommunitiesModel::Roles::DisplayName:
- return tr("Favourites");
- case CommunitiesModel::Roles::Tooltip:
- return tr("Rooms you have favourited.");
- }
- } else if (tag == "m.lowpriority") {
- switch (role) {
- case CommunitiesModel::Roles::AvatarUrl:
- return QString(":/icons/icons/ui/lowprio.png");
- case CommunitiesModel::Roles::DisplayName:
- return tr("Low Priority");
- case CommunitiesModel::Roles::Tooltip:
- return tr("Rooms with low priority.");
- }
- } else if (tag == "m.server_notice") {
- switch (role) {
- case CommunitiesModel::Roles::AvatarUrl:
- return QString(":/icons/icons/ui/tag.png");
- case CommunitiesModel::Roles::DisplayName:
- return tr("Server Notices");
- case CommunitiesModel::Roles::Tooltip:
- return tr("Messages from your server or administrator.");
- }
- } else {
- switch (role) {
- case CommunitiesModel::Roles::AvatarUrl:
- return QString(":/icons/icons/ui/tag.png");
- case CommunitiesModel::Roles::DisplayName:
- case CommunitiesModel::Roles::Tooltip:
- return tag.mid(2);
- }
- }
+ if (index.row() == 0) {
+ switch (role) {
+ case CommunitiesModel::Roles::AvatarUrl:
+ return QString(":/icons/icons/ui/world.png");
+ case CommunitiesModel::Roles::DisplayName:
+ return tr("All rooms");
+ case CommunitiesModel::Roles::Tooltip:
+ return tr("Shows all rooms without filtering.");
+ case CommunitiesModel::Roles::ChildrenHidden:
+ return false;
+ case CommunitiesModel::Roles::Hidden:
+ return false;
+ case CommunitiesModel::Roles::Id:
+ return "";
+ }
+ } else if (index.row() - 1 < spaceOrder_.size()) {
+ auto id = spaceOrder_.at(index.row() - 1);
+ switch (role) {
+ case CommunitiesModel::Roles::AvatarUrl:
+ return QString::fromStdString(spaces_.at(id).avatar_url);
+ case CommunitiesModel::Roles::DisplayName:
+ case CommunitiesModel::Roles::Tooltip:
+ return QString::fromStdString(spaces_.at(id).name);
+ case CommunitiesModel::Roles::ChildrenHidden:
+ return true;
+ case CommunitiesModel::Roles::Hidden:
+ return hiddentTagIds_.contains("space:" + id);
+ case CommunitiesModel::Roles::Id:
+ return "space:" + id;
+ }
+ } else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
+ auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
+ if (tag == "m.favourite") {
+ switch (role) {
+ case CommunitiesModel::Roles::AvatarUrl:
+ return QString(":/icons/icons/ui/star.png");
+ case CommunitiesModel::Roles::DisplayName:
+ return tr("Favourites");
+ case CommunitiesModel::Roles::Tooltip:
+ return tr("Rooms you have favourited.");
+ }
+ } else if (tag == "m.lowpriority") {
+ switch (role) {
+ case CommunitiesModel::Roles::AvatarUrl:
+ return QString(":/icons/icons/ui/lowprio.png");
+ case CommunitiesModel::Roles::DisplayName:
+ return tr("Low Priority");
+ case CommunitiesModel::Roles::Tooltip:
+ return tr("Rooms with low priority.");
+ }
+ } else if (tag == "m.server_notice") {
+ switch (role) {
+ case CommunitiesModel::Roles::AvatarUrl:
+ return QString(":/icons/icons/ui/tag.png");
+ case CommunitiesModel::Roles::DisplayName:
+ return tr("Server Notices");
+ case CommunitiesModel::Roles::Tooltip:
+ return tr("Messages from your server or administrator.");
+ }
+ } else {
+ switch (role) {
+ case CommunitiesModel::Roles::AvatarUrl:
+ return QString(":/icons/icons/ui/tag.png");
+ case CommunitiesModel::Roles::DisplayName:
+ case CommunitiesModel::Roles::Tooltip:
+ return tag.mid(2);
+ }
+ }
- switch (role) {
- case CommunitiesModel::Roles::Hidden:
- return hiddentTagIds_.contains("tag:" + tag);
- case CommunitiesModel::Roles::ChildrenHidden:
- return true;
- case CommunitiesModel::Roles::Id:
- return "tag:" + tag;
- }
+ switch (role) {
+ case CommunitiesModel::Roles::Hidden:
+ return hiddentTagIds_.contains("tag:" + tag);
+ case CommunitiesModel::Roles::ChildrenHidden:
+ return true;
+ case CommunitiesModel::Roles::Id:
+ return "tag:" + tag;
}
- return QVariant();
+ }
+ return QVariant();
}
void
CommunitiesModel::initializeSidebar()
{
- beginResetModel();
- tags_.clear();
- spaceOrder_.clear();
- spaces_.clear();
-
- std::set<std::string> ts;
- std::vector<RoomInfo> tempSpaces;
- auto infos = cache::roomInfo();
- for (auto it = infos.begin(); it != infos.end(); it++) {
- if (it.value().is_space) {
- spaceOrder_.push_back(it.key());
- spaces_[it.key()] = it.value();
- } else {
- for (const auto &t : it.value().tags) {
- if (t.find("u.") == 0 || t.find("m." == 0)) {
- ts.insert(t);
- }
- }
+ beginResetModel();
+ tags_.clear();
+ spaceOrder_.clear();
+ spaces_.clear();
+
+ std::set<std::string> ts;
+ std::vector<RoomInfo> tempSpaces;
+ auto infos = cache::roomInfo();
+ for (auto it = infos.begin(); it != infos.end(); it++) {
+ if (it.value().is_space) {
+ spaceOrder_.push_back(it.key());
+ spaces_[it.key()] = it.value();
+ } else {
+ for (const auto &t : it.value().tags) {
+ if (t.find("u.") == 0 || t.find("m." == 0)) {
+ ts.insert(t);
}
+ }
}
+ }
- for (const auto &t : ts)
- tags_.push_back(QString::fromStdString(t));
+ for (const auto &t : ts)
+ tags_.push_back(QString::fromStdString(t));
- hiddentTagIds_ = UserSettings::instance()->hiddenTags();
- endResetModel();
+ hiddentTagIds_ = UserSettings::instance()->hiddenTags();
+ endResetModel();
- emit tagsChanged();
- emit hiddenTagsChanged();
+ emit tagsChanged();
+ emit hiddenTagsChanged();
}
void
CommunitiesModel::clear()
{
- beginResetModel();
- tags_.clear();
- endResetModel();
- resetCurrentTagId();
+ beginResetModel();
+ tags_.clear();
+ endResetModel();
+ resetCurrentTagId();
- emit tagsChanged();
+ emit tagsChanged();
}
void
CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
{
- bool tagsUpdated = false;
-
- for (const auto &[roomid, room] : rooms.join) {
- (void)roomid;
- for (const auto &e : room.account_data.events)
- if (std::holds_alternative<
- mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
- tagsUpdated = true;
- }
- for (const auto &e : room.state.events)
- if (std::holds_alternative<
- mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
- std::holds_alternative<
- mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
- tagsUpdated = true;
- }
- for (const auto &e : room.timeline.events)
- if (std::holds_alternative<
- mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
- std::holds_alternative<
- mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
- tagsUpdated = true;
- }
- }
- for (const auto &[roomid, room] : rooms.leave) {
- (void)room;
- if (spaceOrder_.contains(QString::fromStdString(roomid)))
- tagsUpdated = true;
- }
-
- if (tagsUpdated)
- initializeSidebar();
+ bool tagsUpdated = false;
+
+ for (const auto &[roomid, room] : rooms.join) {
+ (void)roomid;
+ for (const auto &e : room.account_data.events)
+ if (std::holds_alternative<
+ mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
+ tagsUpdated = true;
+ }
+ for (const auto &e : room.state.events)
+ if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
+ e) ||
+ std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
+ e)) {
+ tagsUpdated = true;
+ }
+ for (const auto &e : room.timeline.events)
+ if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
+ e) ||
+ std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
+ e)) {
+ tagsUpdated = true;
+ }
+ }
+ for (const auto &[roomid, room] : rooms.leave) {
+ (void)room;
+ if (spaceOrder_.contains(QString::fromStdString(roomid)))
+ tagsUpdated = true;
+ }
+
+ if (tagsUpdated)
+ initializeSidebar();
}
void
CommunitiesModel::setCurrentTagId(QString tagId)
{
- if (tagId.startsWith("tag:")) {
- auto tag = tagId.mid(4);
- for (const auto &t : tags_) {
- if (t == tag) {
- this->currentTagId_ = tagId;
- emit currentTagIdChanged(currentTagId_);
- return;
- }
- }
- } else if (tagId.startsWith("space:")) {
- auto tag = tagId.mid(6);
- for (const auto &t : spaceOrder_) {
- if (t == tag) {
- this->currentTagId_ = tagId;
- emit currentTagIdChanged(currentTagId_);
- return;
- }
- }
+ if (tagId.startsWith("tag:")) {
+ auto tag = tagId.mid(4);
+ for (const auto &t : tags_) {
+ if (t == tag) {
+ this->currentTagId_ = tagId;
+ emit currentTagIdChanged(currentTagId_);
+ return;
+ }
+ }
+ } else if (tagId.startsWith("space:")) {
+ auto tag = tagId.mid(6);
+ for (const auto &t : spaceOrder_) {
+ if (t == tag) {
+ this->currentTagId_ = tagId;
+ emit currentTagIdChanged(currentTagId_);
+ return;
+ }
}
+ }
- this->currentTagId_ = "";
- emit currentTagIdChanged(currentTagId_);
+ this->currentTagId_ = "";
+ emit currentTagIdChanged(currentTagId_);
}
void
CommunitiesModel::toggleTagId(QString tagId)
{
- if (hiddentTagIds_.contains(tagId)) {
- hiddentTagIds_.removeOne(tagId);
- UserSettings::instance()->setHiddenTags(hiddentTagIds_);
- } else {
- hiddentTagIds_.push_back(tagId);
- UserSettings::instance()->setHiddenTags(hiddentTagIds_);
- }
-
- if (tagId.startsWith("tag:")) {
- auto idx = tags_.indexOf(tagId.mid(4));
- if (idx != -1)
- emit dataChanged(index(idx + 1 + spaceOrder_.size()),
- index(idx + 1 + spaceOrder_.size()),
- {Hidden});
- } else if (tagId.startsWith("space:")) {
- auto idx = spaceOrder_.indexOf(tagId.mid(6));
- if (idx != -1)
- emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
- }
-
- emit hiddenTagsChanged();
+ if (hiddentTagIds_.contains(tagId)) {
+ hiddentTagIds_.removeOne(tagId);
+ UserSettings::instance()->setHiddenTags(hiddentTagIds_);
+ } else {
+ hiddentTagIds_.push_back(tagId);
+ UserSettings::instance()->setHiddenTags(hiddentTagIds_);
+ }
+
+ if (tagId.startsWith("tag:")) {
+ auto idx = tags_.indexOf(tagId.mid(4));
+ if (idx != -1)
+ emit dataChanged(
+ index(idx + 1 + spaceOrder_.size()), index(idx + 1 + spaceOrder_.size()), {Hidden});
+ } else if (tagId.startsWith("space:")) {
+ auto idx = spaceOrder_.indexOf(tagId.mid(6));
+ if (idx != -1)
+ emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
+ }
+
+ emit hiddenTagsChanged();
}
diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h
index 677581dc..0440d17f 100644
--- a/src/timeline/CommunitiesModel.h
+++ b/src/timeline/CommunitiesModel.h
@@ -15,64 +15,64 @@
class CommunitiesModel : public QAbstractListModel
{
- Q_OBJECT
- Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
- currentTagIdChanged RESET resetCurrentTagId)
- Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
- Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged)
+ Q_OBJECT
+ Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
+ currentTagIdChanged RESET resetCurrentTagId)
+ Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
+ Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged)
public:
- enum Roles
- {
- AvatarUrl = Qt::UserRole,
- DisplayName,
- Tooltip,
- ChildrenHidden,
- Hidden,
- Id,
- };
+ enum Roles
+ {
+ AvatarUrl = Qt::UserRole,
+ DisplayName,
+ Tooltip,
+ ChildrenHidden,
+ Hidden,
+ Id,
+ };
- CommunitiesModel(QObject *parent = nullptr);
- QHash<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override
- {
- (void)parent;
- return 1 + tags_.size() + spaceOrder_.size();
- }
- QVariant data(const QModelIndex &index, int role) const override;
+ CommunitiesModel(QObject *parent = nullptr);
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override
+ {
+ (void)parent;
+ return 1 + tags_.size() + spaceOrder_.size();
+ }
+ QVariant data(const QModelIndex &index, int role) const override;
public slots:
- void initializeSidebar();
- void sync(const mtx::responses::Rooms &rooms);
- void clear();
- QString currentTagId() const { return currentTagId_; }
- void setCurrentTagId(QString tagId);
- void resetCurrentTagId()
- {
- currentTagId_.clear();
- emit currentTagIdChanged(currentTagId_);
- }
- QStringList tags() const { return tags_; }
- QStringList tagsWithDefault() const
- {
- QStringList tagsWD = tags_;
- tagsWD.prepend("m.lowpriority");
- tagsWD.prepend("m.favourite");
- tagsWD.removeOne("m.server_notice");
- tagsWD.removeDuplicates();
- return tagsWD;
- }
- void toggleTagId(QString tagId);
+ void initializeSidebar();
+ void sync(const mtx::responses::Rooms &rooms);
+ void clear();
+ QString currentTagId() const { return currentTagId_; }
+ void setCurrentTagId(QString tagId);
+ void resetCurrentTagId()
+ {
+ currentTagId_.clear();
+ emit currentTagIdChanged(currentTagId_);
+ }
+ QStringList tags() const { return tags_; }
+ QStringList tagsWithDefault() const
+ {
+ QStringList tagsWD = tags_;
+ tagsWD.prepend("m.lowpriority");
+ tagsWD.prepend("m.favourite");
+ tagsWD.removeOne("m.server_notice");
+ tagsWD.removeDuplicates();
+ return tagsWD;
+ }
+ void toggleTagId(QString tagId);
signals:
- void currentTagIdChanged(QString tagId);
- void hiddenTagsChanged();
- void tagsChanged();
+ void currentTagIdChanged(QString tagId);
+ void hiddenTagsChanged();
+ void tagsChanged();
private:
- QStringList tags_;
- QString currentTagId_;
- QStringList hiddentTagIds_;
- QStringList spaceOrder_;
- std::map<QString, RoomInfo> spaces_;
+ QStringList tags_;
+ QString currentTagId_;
+ QStringList hiddentTagIds_;
+ QStringList spaceOrder_;
+ std::map<QString, RoomInfo> spaces_;
};
diff --git a/src/timeline/DelegateChooser.cpp b/src/timeline/DelegateChooser.cpp
index 39c8fa17..682077ae 100644
--- a/src/timeline/DelegateChooser.cpp
+++ b/src/timeline/DelegateChooser.cpp
@@ -13,127 +13,126 @@
QQmlComponent *
DelegateChoice::delegate() const
{
- return delegate_;
+ return delegate_;
}
void
DelegateChoice::setDelegate(QQmlComponent *delegate)
{
- if (delegate != delegate_) {
- delegate_ = delegate;
- emit delegateChanged();
- emit changed();
- }
+ if (delegate != delegate_) {
+ delegate_ = delegate;
+ emit delegateChanged();
+ emit changed();
+ }
}
QVariant
DelegateChoice::roleValue() const
{
- return roleValue_;
+ return roleValue_;
}
void
DelegateChoice::setRoleValue(const QVariant &value)
{
- if (value != roleValue_) {
- roleValue_ = value;
- emit roleValueChanged();
- emit changed();
- }
+ if (value != roleValue_) {
+ roleValue_ = value;
+ emit roleValueChanged();
+ emit changed();
+ }
}
QVariant
DelegateChooser::roleValue() const
{
- return roleValue_;
+ return roleValue_;
}
void
DelegateChooser::setRoleValue(const QVariant &value)
{
- if (value != roleValue_) {
- roleValue_ = value;
- recalcChild();
- emit roleValueChanged();
- }
+ if (value != roleValue_) {
+ roleValue_ = value;
+ recalcChild();
+ emit roleValueChanged();
+ }
}
QQmlListProperty<DelegateChoice>
DelegateChooser::choices()
{
- return QQmlListProperty<DelegateChoice>(this,
- this,
- &DelegateChooser::appendChoice,
- &DelegateChooser::choiceCount,
- &DelegateChooser::choice,
- &DelegateChooser::clearChoices);
+ return QQmlListProperty<DelegateChoice>(this,
+ this,
+ &DelegateChooser::appendChoice,
+ &DelegateChooser::choiceCount,
+ &DelegateChooser::choice,
+ &DelegateChooser::clearChoices);
}
void
DelegateChooser::appendChoice(QQmlListProperty<DelegateChoice> *p, DelegateChoice *c)
{
- DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
- dc->choices_.append(c);
+ DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
+ dc->choices_.append(c);
}
int
DelegateChooser::choiceCount(QQmlListProperty<DelegateChoice> *p)
{
- return static_cast<DelegateChooser *>(p->object)->choices_.count();
+ return static_cast<DelegateChooser *>(p->object)->choices_.count();
}
DelegateChoice *
DelegateChooser::choice(QQmlListProperty<DelegateChoice> *p, int index)
{
- return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
+ return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
}
void
DelegateChooser::clearChoices(QQmlListProperty<DelegateChoice> *p)
{
- static_cast<DelegateChooser *>(p->object)->choices_.clear();
+ static_cast<DelegateChooser *>(p->object)->choices_.clear();
}
void
DelegateChooser::recalcChild()
{
- for (const auto choice : qAsConst(choices_)) {
- auto choiceValue = choice->roleValue();
- if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
- if (child_) {
- child_->setParentItem(nullptr);
- child_ = nullptr;
- }
-
- choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
- return;
- }
+ for (const auto choice : qAsConst(choices_)) {
+ auto choiceValue = choice->roleValue();
+ if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
+ if (child_) {
+ child_->setParentItem(nullptr);
+ child_ = nullptr;
+ }
+
+ choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
+ return;
}
+ }
}
void
DelegateChooser::componentComplete()
{
- QQuickItem::componentComplete();
- recalcChild();
+ QQuickItem::componentComplete();
+ recalcChild();
}
void
DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
{
- if (status == QQmlIncubator::Ready) {
- chooser.child_ = dynamic_cast<QQuickItem *>(object());
- if (chooser.child_ == nullptr) {
- nhlog::ui()->error("Delegate has to be derived of Item!");
- return;
- }
-
- chooser.child_->setParentItem(&chooser);
- QQmlEngine::setObjectOwnership(chooser.child_,
- QQmlEngine::ObjectOwnership::JavaScriptOwnership);
- emit chooser.childChanged();
-
- } else if (status == QQmlIncubator::Error) {
- for (const auto &e : errors())
- nhlog::ui()->error("Error instantiating delegate: {}",
- e.toString().toStdString());
+ if (status == QQmlIncubator::Ready) {
+ chooser.child_ = dynamic_cast<QQuickItem *>(object());
+ if (chooser.child_ == nullptr) {
+ nhlog::ui()->error("Delegate has to be derived of Item!");
+ return;
}
+
+ chooser.child_->setParentItem(&chooser);
+ QQmlEngine::setObjectOwnership(chooser.child_,
+ QQmlEngine::ObjectOwnership::JavaScriptOwnership);
+ emit chooser.childChanged();
+
+ } else if (status == QQmlIncubator::Error) {
+ for (const auto &e : errors())
+ nhlog::ui()->error("Error instantiating delegate: {}", e.toString().toStdString());
+ }
}
diff --git a/src/timeline/DelegateChooser.h b/src/timeline/DelegateChooser.h
index 22e423a2..3e4b16d7 100644
--- a/src/timeline/DelegateChooser.h
+++ b/src/timeline/DelegateChooser.h
@@ -18,73 +18,73 @@ class QQmlAdaptorModel;
class DelegateChoice : public QObject
{
- Q_OBJECT
- Q_CLASSINFO("DefaultProperty", "delegate")
+ Q_OBJECT
+ Q_CLASSINFO("DefaultProperty", "delegate")
public:
- Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
- Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
- QQmlComponent *delegate() const;
- void setDelegate(QQmlComponent *delegate);
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *delegate);
- QVariant roleValue() const;
- void setRoleValue(const QVariant &value);
+ QVariant roleValue() const;
+ void setRoleValue(const QVariant &value);
signals:
- void delegateChanged();
- void roleValueChanged();
- void changed();
+ void delegateChanged();
+ void roleValueChanged();
+ void changed();
private:
- QVariant roleValue_;
- QQmlComponent *delegate_ = nullptr;
+ QVariant roleValue_;
+ QQmlComponent *delegate_ = nullptr;
};
class DelegateChooser : public QQuickItem
{
- Q_OBJECT
- Q_CLASSINFO("DefaultProperty", "choices")
+ Q_OBJECT
+ Q_CLASSINFO("DefaultProperty", "choices")
public:
- Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
- Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
- Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged)
+ Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
+ Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
+ Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged)
- QQmlListProperty<DelegateChoice> choices();
+ QQmlListProperty<DelegateChoice> choices();
- QVariant roleValue() const;
- void setRoleValue(const QVariant &value);
+ QVariant roleValue() const;
+ void setRoleValue(const QVariant &value);
- QQuickItem *child() const { return child_; }
+ QQuickItem *child() const { return child_; }
- void recalcChild();
- void componentComplete() override;
+ void recalcChild();
+ void componentComplete() override;
signals:
- void roleChanged();
- void roleValueChanged();
- void childChanged();
+ void roleChanged();
+ void roleValueChanged();
+ void childChanged();
private:
- struct DelegateIncubator : public QQmlIncubator
- {
- DelegateIncubator(DelegateChooser &parent)
- : QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
- , chooser(parent)
- {}
- void statusChanged(QQmlIncubator::Status status) override;
-
- DelegateChooser &chooser;
- };
-
- QVariant roleValue_;
- QList<DelegateChoice *> choices_;
- QQuickItem *child_ = nullptr;
- DelegateIncubator incubator{*this};
-
- static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
- static int choiceCount(QQmlListProperty<DelegateChoice> *);
- static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
- static void clearChoices(QQmlListProperty<DelegateChoice> *);
+ struct DelegateIncubator : public QQmlIncubator
+ {
+ DelegateIncubator(DelegateChooser &parent)
+ : QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
+ , chooser(parent)
+ {}
+ void statusChanged(QQmlIncubator::Status status) override;
+
+ DelegateChooser &chooser;
+ };
+
+ QVariant roleValue_;
+ QList<DelegateChoice *> choices_;
+ QQuickItem *child_ = nullptr;
+ DelegateIncubator incubator{*this};
+
+ static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
+ static int choiceCount(QQmlListProperty<DelegateChoice> *);
+ static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
+ static void clearChoices(QQmlListProperty<DelegateChoice> *);
};
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 881fd5bb..7144424a 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -28,393 +28,373 @@ QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::
EventStore::EventStore(std::string room_id, QObject *)
: room_id_(std::move(room_id))
{
- static auto reactionType = qRegisterMetaType<Reaction>();
- (void)reactionType;
-
- auto range = cache::client()->getTimelineRange(room_id_);
-
- if (range) {
- this->first = range->first;
- this->last = range->last;
- }
-
- connect(
- this,
- &EventStore::eventFetched,
- this,
- [this](std::string id,
- std::string relatedTo,
- mtx::events::collections::TimelineEvents timeline) {
- cache::client()->storeEvent(room_id_, id, {timeline});
-
- if (!relatedTo.empty()) {
- auto idx = idToIndex(relatedTo);
- if (idx)
- emit dataChanged(*idx, *idx);
- }
- },
- Qt::QueuedConnection);
-
- connect(
- this,
- &EventStore::oldMessagesRetrieved,
- this,
- [this](const mtx::responses::Messages &res) {
- if (res.end.empty() || cache::client()->previousBatchToken(room_id_) == res.end) {
- noMoreMessages = true;
- emit fetchedMore();
- return;
+ static auto reactionType = qRegisterMetaType<Reaction>();
+ (void)reactionType;
+
+ auto range = cache::client()->getTimelineRange(room_id_);
+
+ if (range) {
+ this->first = range->first;
+ this->last = range->last;
+ }
+
+ connect(
+ this,
+ &EventStore::eventFetched,
+ this,
+ [this](
+ std::string id, std::string relatedTo, mtx::events::collections::TimelineEvents timeline) {
+ cache::client()->storeEvent(room_id_, id, {timeline});
+
+ if (!relatedTo.empty()) {
+ auto idx = idToIndex(relatedTo);
+ if (idx)
+ emit dataChanged(*idx, *idx);
+ }
+ },
+ Qt::QueuedConnection);
+
+ connect(
+ this,
+ &EventStore::oldMessagesRetrieved,
+ this,
+ [this](const mtx::responses::Messages &res) {
+ if (res.end.empty() || cache::client()->previousBatchToken(room_id_) == res.end) {
+ noMoreMessages = true;
+ emit fetchedMore();
+ return;
+ }
+
+ uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
+ if (newFirst == first)
+ fetchMore();
+ else {
+ if (this->last != std::numeric_limits<uint64_t>::max()) {
+ auto oldFirst = this->first;
+ emit beginInsertRows(toExternalIdx(newFirst), toExternalIdx(this->first - 1));
+ this->first = newFirst;
+ emit endInsertRows();
+ emit fetchedMore();
+ emit dataChanged(toExternalIdx(oldFirst), toExternalIdx(oldFirst));
+ } else {
+ auto range = cache::client()->getTimelineRange(room_id_);
+
+ if (range && range->last - range->first != 0) {
+ emit beginInsertRows(0, int(range->last - range->first));
+ this->first = range->first;
+ this->last = range->last;
+ emit endInsertRows();
+ emit fetchedMore();
+ } else {
+ fetchMore();
}
+ }
+ }
+ },
+ Qt::QueuedConnection);
+
+ connect(this, &EventStore::processPending, this, [this]() {
+ if (!current_txn.empty()) {
+ nhlog::ui()->debug("Already processing {}", current_txn);
+ return;
+ }
- uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
- if (newFirst == first)
- fetchMore();
- else {
- if (this->last != std::numeric_limits<uint64_t>::max()) {
- auto oldFirst = this->first;
- emit beginInsertRows(toExternalIdx(newFirst),
- toExternalIdx(this->first - 1));
- this->first = newFirst;
- emit endInsertRows();
- emit fetchedMore();
- emit dataChanged(toExternalIdx(oldFirst),
- toExternalIdx(oldFirst));
- } else {
- auto range = cache::client()->getTimelineRange(room_id_);
-
- if (range && range->last - range->first != 0) {
- emit beginInsertRows(0, int(range->last - range->first));
- this->first = range->first;
- this->last = range->last;
- emit endInsertRows();
- emit fetchedMore();
- } else {
- fetchMore();
- }
- }
- }
- },
- Qt::QueuedConnection);
+ auto event = cache::client()->firstPendingMessage(room_id_);
- connect(this, &EventStore::processPending, this, [this]() {
- if (!current_txn.empty()) {
- nhlog::ui()->debug("Already processing {}", current_txn);
- return;
- }
+ if (!event) {
+ nhlog::ui()->debug("No event to send");
+ return;
+ }
- auto event = cache::client()->firstPendingMessage(room_id_);
+ std::visit(
+ [this](auto e) {
+ auto txn_id = e.event_id;
+ this->current_txn = txn_id;
- if (!event) {
- nhlog::ui()->debug("No event to send");
- return;
- }
+ if (txn_id.empty() || txn_id[0] != 'm') {
+ nhlog::ui()->debug("Invalid txn id '{}'", txn_id);
+ cache::client()->removePendingStatus(room_id_, txn_id);
+ return;
+ }
+
+ if constexpr (mtx::events::message_content_to_type<decltype(e.content)> !=
+ mtx::events::EventType::Unsupported)
+ http::client()->send_room_message(
+ room_id_,
+ txn_id,
+ e.content,
+ [this, txn_id, e](const mtx::responses::EventId &event_id,
+ mtx::http::RequestErr err) {
+ if (err) {
+ const int status_code = static_cast<int>(err->status_code);
+ nhlog::net()->warn("[{}] failed to send message: {} {}",
+ txn_id,
+ err->matrix_error.error,
+ status_code);
+ emit messageFailed(txn_id);
+ return;
+ }
- std::visit(
- [this](auto e) {
- auto txn_id = e.event_id;
- this->current_txn = txn_id;
-
- if (txn_id.empty() || txn_id[0] != 'm') {
- nhlog::ui()->debug("Invalid txn id '{}'", txn_id);
- cache::client()->removePendingStatus(room_id_, txn_id);
- return;
- }
-
- if constexpr (mtx::events::message_content_to_type<decltype(e.content)> !=
- mtx::events::EventType::Unsupported)
- http::client()->send_room_message(
- room_id_,
- txn_id,
- e.content,
- [this, txn_id, e](const mtx::responses::EventId &event_id,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code =
- static_cast<int>(err->status_code);
- nhlog::net()->warn(
- "[{}] failed to send message: {} {}",
- txn_id,
- err->matrix_error.error,
- status_code);
- emit messageFailed(txn_id);
- return;
- }
-
- emit messageSent(txn_id, event_id.event_id.to_string());
- if constexpr (std::is_same_v<
- decltype(e.content),
- mtx::events::msg::Encrypted>) {
- auto event =
- decryptEvent({room_id_, e.event_id}, e);
- if (event->event) {
- if (auto dec = std::get_if<
- mtx::events::RoomEvent<
- mtx::events::msg::
- KeyVerificationRequest>>(
- &event->event.value())) {
- emit updateFlowEventId(
- event_id.event_id
- .to_string());
- }
- }
- }
- });
- },
- event->data);
- });
-
- connect(
- this,
- &EventStore::messageFailed,
- this,
- [this](std::string txn_id) {
- if (current_txn == txn_id) {
- current_txn_error_count++;
- if (current_txn_error_count > 10) {
- nhlog::ui()->debug("failing txn id '{}'", txn_id);
- cache::client()->removePendingStatus(room_id_, txn_id);
- current_txn_error_count = 0;
- }
- }
- QTimer::singleShot(1000, this, [this]() {
- nhlog::ui()->debug("timeout");
- this->current_txn = "";
- emit processPending();
- });
+ emit messageSent(txn_id, event_id.event_id.to_string());
+ if constexpr (std::is_same_v<decltype(e.content),
+ mtx::events::msg::Encrypted>) {
+ auto event = decryptEvent({room_id_, e.event_id}, e);
+ if (event->event) {
+ if (auto dec = std::get_if<mtx::events::RoomEvent<
+ mtx::events::msg::KeyVerificationRequest>>(
+ &event->event.value())) {
+ emit updateFlowEventId(event_id.event_id.to_string());
+ }
+ }
+ }
+ });
},
- Qt::QueuedConnection);
-
- connect(
- this,
- &EventStore::messageSent,
- this,
- [this](std::string txn_id, std::string event_id) {
- nhlog::ui()->debug("sent {}", txn_id);
-
- // Replace the event_id in pending edits/replies/redactions with the actual
- // event_id of this event. This allows one to edit and reply to events that are
- // currently pending.
-
- // FIXME (introduced by balsoft): this doesn't work for encrypted events, but
- // allegedly it's hard to fix so I'll leave my first contribution at that
- for (auto related_event_id : cache::client()->relatedEvents(room_id_, txn_id)) {
- if (cache::client()->getEvent(room_id_, related_event_id)) {
- auto related_event =
- cache::client()->getEvent(room_id_, related_event_id).value();
- auto relations = mtx::accessors::relations(related_event.data);
-
- // Replace the blockquote in fallback reply
- auto related_text =
- std::get_if<mtx::events::RoomEvent<mtx::events::msg::Text>>(
- &related_event.data);
- if (related_text && relations.reply_to() == txn_id) {
- size_t index =
- related_text->content.formatted_body.find(txn_id);
- if (index != std::string::npos) {
- related_text->content.formatted_body.replace(
- index, event_id.length(), event_id);
- }
- }
+ event->data);
+ });
+
+ connect(
+ this,
+ &EventStore::messageFailed,
+ this,
+ [this](std::string txn_id) {
+ if (current_txn == txn_id) {
+ current_txn_error_count++;
+ if (current_txn_error_count > 10) {
+ nhlog::ui()->debug("failing txn id '{}'", txn_id);
+ cache::client()->removePendingStatus(room_id_, txn_id);
+ current_txn_error_count = 0;
+ }
+ }
+ QTimer::singleShot(1000, this, [this]() {
+ nhlog::ui()->debug("timeout");
+ this->current_txn = "";
+ emit processPending();
+ });
+ },
+ Qt::QueuedConnection);
+
+ connect(
+ this,
+ &EventStore::messageSent,
+ this,
+ [this](std::string txn_id, std::string event_id) {
+ nhlog::ui()->debug("sent {}", txn_id);
+
+ // Replace the event_id in pending edits/replies/redactions with the actual
+ // event_id of this event. This allows one to edit and reply to events that are
+ // currently pending.
+
+ // FIXME (introduced by balsoft): this doesn't work for encrypted events, but
+ // allegedly it's hard to fix so I'll leave my first contribution at that
+ for (auto related_event_id : cache::client()->relatedEvents(room_id_, txn_id)) {
+ if (cache::client()->getEvent(room_id_, related_event_id)) {
+ auto related_event =
+ cache::client()->getEvent(room_id_, related_event_id).value();
+ auto relations = mtx::accessors::relations(related_event.data);
+
+ // Replace the blockquote in fallback reply
+ auto related_text = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Text>>(
+ &related_event.data);
+ if (related_text && relations.reply_to() == txn_id) {
+ size_t index = related_text->content.formatted_body.find(txn_id);
+ if (index != std::string::npos) {
+ related_text->content.formatted_body.replace(
+ index, event_id.length(), event_id);
+ }
+ }
- for (mtx::common::Relation &rel : relations.relations) {
- if (rel.event_id == txn_id)
- rel.event_id = event_id;
- }
+ for (mtx::common::Relation &rel : relations.relations) {
+ if (rel.event_id == txn_id)
+ rel.event_id = event_id;
+ }
- mtx::accessors::set_relations(related_event.data, relations);
+ mtx::accessors::set_relations(related_event.data, relations);
- cache::client()->replaceEvent(
- room_id_, related_event_id, related_event);
+ cache::client()->replaceEvent(room_id_, related_event_id, related_event);
- auto idx = idToIndex(related_event_id);
+ auto idx = idToIndex(related_event_id);
- events_by_id_.remove({room_id_, related_event_id});
- events_.remove({room_id_, toInternalIdx(*idx)});
- }
- }
+ events_by_id_.remove({room_id_, related_event_id});
+ events_.remove({room_id_, toInternalIdx(*idx)});
+ }
+ }
- http::client()->read_event(
- room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn(
- "failed to read_event ({}, {})", room_id_, event_id);
- }
- });
+ http::client()->read_event(
+ room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to read_event ({}, {})", room_id_, event_id);
+ }
+ });
- auto idx = idToIndex(event_id);
+ auto idx = idToIndex(event_id);
- if (idx)
- emit dataChanged(*idx, *idx);
+ if (idx)
+ emit dataChanged(*idx, *idx);
- cache::client()->removePendingStatus(room_id_, txn_id);
- this->current_txn = "";
- this->current_txn_error_count = 0;
- emit processPending();
- },
- Qt::QueuedConnection);
+ cache::client()->removePendingStatus(room_id_, txn_id);
+ this->current_txn = "";
+ this->current_txn_error_count = 0;
+ emit processPending();
+ },
+ Qt::QueuedConnection);
}
void
EventStore::addPending(mtx::events::collections::TimelineEvents event)
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
- cache::client()->savePendingMessage(this->room_id_, {event});
- mtx::responses::Timeline events;
- events.limited = false;
- events.events.emplace_back(event);
- handleSync(events);
+ cache::client()->savePendingMessage(this->room_id_, {event});
+ mtx::responses::Timeline events;
+ events.limited = false;
+ events.events.emplace_back(event);
+ handleSync(events);
- emit processPending();
+ emit processPending();
}
void
EventStore::clearTimeline()
{
- emit beginResetModel();
-
- cache::client()->clearTimeline(room_id_);
- auto range = cache::client()->getTimelineRange(room_id_);
- if (range) {
- nhlog::db()->info("Range {} {}", range->last, range->first);
- this->last = range->last;
- this->first = range->first;
- } else {
- this->first = std::numeric_limits<uint64_t>::max();
- this->last = std::numeric_limits<uint64_t>::max();
- }
- nhlog::ui()->info("Range {} {}", this->last, this->first);
-
- decryptedEvents_.clear();
- events_.clear();
-
- emit endResetModel();
+ emit beginResetModel();
+
+ cache::client()->clearTimeline(room_id_);
+ auto range = cache::client()->getTimelineRange(room_id_);
+ if (range) {
+ nhlog::db()->info("Range {} {}", range->last, range->first);
+ this->last = range->last;
+ this->first = range->first;
+ } else {
+ this->first = std::numeric_limits<uint64_t>::max();
+ this->last = std::numeric_limits<uint64_t>::max();
+ }
+ nhlog::ui()->info("Range {} {}", this->last, this->first);
+
+ decryptedEvents_.clear();
+ events_.clear();
+
+ emit endResetModel();
}
void
EventStore::receivedSessionKey(const std::string &session_id)
{
- if (!pending_key_requests.count(session_id))
- return;
+ if (!pending_key_requests.count(session_id))
+ return;
- auto request = pending_key_requests.at(session_id);
+ auto request = pending_key_requests.at(session_id);
- // Don't request keys again until Nheko is restarted (for now)
- pending_key_requests[session_id].events.clear();
+ // Don't request keys again until Nheko is restarted (for now)
+ pending_key_requests[session_id].events.clear();
- if (!request.events.empty())
- olm::send_key_request_for(request.events.front(), request.request_id, true);
+ if (!request.events.empty())
+ olm::send_key_request_for(request.events.front(), request.request_id, true);
- for (const auto &e : request.events) {
- auto idx = idToIndex(e.event_id);
- if (idx) {
- decryptedEvents_.remove({room_id_, e.event_id});
- events_by_id_.remove({room_id_, e.event_id});
- events_.remove({room_id_, toInternalIdx(*idx)});
- emit dataChanged(*idx, *idx);
- }
+ for (const auto &e : request.events) {
+ auto idx = idToIndex(e.event_id);
+ if (idx) {
+ decryptedEvents_.remove({room_id_, e.event_id});
+ events_by_id_.remove({room_id_, e.event_id});
+ events_.remove({room_id_, toInternalIdx(*idx)});
+ emit dataChanged(*idx, *idx);
}
+ }
}
void
EventStore::handleSync(const mtx::responses::Timeline &events)
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
-
- auto range = cache::client()->getTimelineRange(room_id_);
- if (!range) {
- emit beginResetModel();
- this->first = std::numeric_limits<uint64_t>::max();
- this->last = std::numeric_limits<uint64_t>::max();
-
- decryptedEvents_.clear();
- events_.clear();
- emit endResetModel();
- return;
- }
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
- if (events.limited) {
- emit beginResetModel();
- this->last = range->last;
- this->first = range->first;
-
- decryptedEvents_.clear();
- events_.clear();
- emit endResetModel();
- } else if (range->last > this->last) {
- emit beginInsertRows(toExternalIdx(this->last + 1), toExternalIdx(range->last));
- this->last = range->last;
- emit endInsertRows();
- }
+ auto range = cache::client()->getTimelineRange(room_id_);
+ if (!range) {
+ emit beginResetModel();
+ this->first = std::numeric_limits<uint64_t>::max();
+ this->last = std::numeric_limits<uint64_t>::max();
- for (const auto &event : events.events) {
- std::set<std::string> relates_to;
- if (auto redaction =
- std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(
- &event)) {
- // fixup reactions
- auto redacted = events_by_id_.object({room_id_, redaction->redacts});
- if (redacted) {
- auto id = mtx::accessors::relations(*redacted);
- if (id.annotates()) {
- auto idx = idToIndex(id.annotates()->event_id);
- if (idx) {
- events_by_id_.remove(
- {room_id_, redaction->redacts});
- events_.remove({room_id_, toInternalIdx(*idx)});
- emit dataChanged(*idx, *idx);
- }
- }
- }
+ decryptedEvents_.clear();
+ events_.clear();
+ emit endResetModel();
+ return;
+ }
- relates_to.insert(redaction->redacts);
- } else {
- for (const auto &r : mtx::accessors::relations(event).relations)
- relates_to.insert(r.event_id);
- }
+ if (events.limited) {
+ emit beginResetModel();
+ this->last = range->last;
+ this->first = range->first;
- for (const auto &relates_to_id : relates_to) {
- auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id);
- if (idx) {
- events_by_id_.remove({room_id_, relates_to_id});
- decryptedEvents_.remove({room_id_, relates_to_id});
- events_.remove({room_id_, *idx});
- emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
- }
+ decryptedEvents_.clear();
+ events_.clear();
+ emit endResetModel();
+ } else if (range->last > this->last) {
+ emit beginInsertRows(toExternalIdx(this->last + 1), toExternalIdx(range->last));
+ this->last = range->last;
+ emit endInsertRows();
+ }
+
+ for (const auto &event : events.events) {
+ std::set<std::string> relates_to;
+ if (auto redaction =
+ std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&event)) {
+ // fixup reactions
+ auto redacted = events_by_id_.object({room_id_, redaction->redacts});
+ if (redacted) {
+ auto id = mtx::accessors::relations(*redacted);
+ if (id.annotates()) {
+ auto idx = idToIndex(id.annotates()->event_id);
+ if (idx) {
+ events_by_id_.remove({room_id_, redaction->redacts});
+ events_.remove({room_id_, toInternalIdx(*idx)});
+ emit dataChanged(*idx, *idx);
+ }
}
+ }
- if (auto txn_id = mtx::accessors::transaction_id(event); !txn_id.empty()) {
- auto idx = cache::client()->getTimelineIndex(
- room_id_, mtx::accessors::event_id(event));
- if (idx) {
- Index index{room_id_, *idx};
- events_.remove(index);
- emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
- }
- }
+ relates_to.insert(redaction->redacts);
+ } else {
+ for (const auto &r : mtx::accessors::relations(event).relations)
+ relates_to.insert(r.event_id);
+ }
- // decrypting and checking some encrypted messages
- if (auto encrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- &event)) {
- auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
- if (d_event->event &&
- std::visit(
- [](auto e) { return (e.sender != utils::localUser().toStdString()); },
- *d_event->event)) {
- handle_room_verification(*d_event->event);
- }
- }
+ for (const auto &relates_to_id : relates_to) {
+ auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id);
+ if (idx) {
+ events_by_id_.remove({room_id_, relates_to_id});
+ decryptedEvents_.remove({room_id_, relates_to_id});
+ events_.remove({room_id_, *idx});
+ emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
+ }
+ }
+
+ if (auto txn_id = mtx::accessors::transaction_id(event); !txn_id.empty()) {
+ auto idx = cache::client()->getTimelineIndex(room_id_, mtx::accessors::event_id(event));
+ if (idx) {
+ Index index{room_id_, *idx};
+ events_.remove(index);
+ emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
+ }
}
+
+ // decrypting and checking some encrypted messages
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
+ auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+ if (d_event->event &&
+ std::visit([](auto e) { return (e.sender != utils::localUser().toStdString()); },
+ *d_event->event)) {
+ handle_room_verification(*d_event->event);
+ }
+ }
+ }
}
namespace {
template<class... Ts>
struct overloaded : Ts...
{
- using Ts::operator()...;
+ using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
@@ -423,462 +403,451 @@ overloaded(Ts...) -> overloaded<Ts...>;
void
EventStore::handle_room_verification(mtx::events::collections::TimelineEvents event)
{
- std::visit(
- overloaded{
- [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) {
- nhlog::db()->debug("handle_room_verification: Request");
- emit startDMVerification(msg);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg) {
- nhlog::db()->debug("handle_room_verification: Cancel");
- ChatPage::instance()->receivedDeviceVerificationCancel(msg.content);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg) {
- nhlog::db()->debug("handle_room_verification: Accept");
- ChatPage::instance()->receivedDeviceVerificationAccept(msg.content);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg) {
- nhlog::db()->debug("handle_room_verification: Key");
- ChatPage::instance()->receivedDeviceVerificationKey(msg.content);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg) {
- nhlog::db()->debug("handle_room_verification: Mac");
- ChatPage::instance()->receivedDeviceVerificationMac(msg.content);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg) {
- nhlog::db()->debug("handle_room_verification: Ready");
- ChatPage::instance()->receivedDeviceVerificationReady(msg.content);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg) {
- nhlog::db()->debug("handle_room_verification: Done");
- ChatPage::instance()->receivedDeviceVerificationDone(msg.content);
- },
- [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg) {
- nhlog::db()->debug("handle_room_verification: Start");
- ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender);
- },
- [](const auto &) {},
- },
- event);
+ std::visit(
+ overloaded{
+ [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) {
+ nhlog::db()->debug("handle_room_verification: Request");
+ emit startDMVerification(msg);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg) {
+ nhlog::db()->debug("handle_room_verification: Cancel");
+ ChatPage::instance()->receivedDeviceVerificationCancel(msg.content);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg) {
+ nhlog::db()->debug("handle_room_verification: Accept");
+ ChatPage::instance()->receivedDeviceVerificationAccept(msg.content);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg) {
+ nhlog::db()->debug("handle_room_verification: Key");
+ ChatPage::instance()->receivedDeviceVerificationKey(msg.content);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg) {
+ nhlog::db()->debug("handle_room_verification: Mac");
+ ChatPage::instance()->receivedDeviceVerificationMac(msg.content);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg) {
+ nhlog::db()->debug("handle_room_verification: Ready");
+ ChatPage::instance()->receivedDeviceVerificationReady(msg.content);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg) {
+ nhlog::db()->debug("handle_room_verification: Done");
+ ChatPage::instance()->receivedDeviceVerificationDone(msg.content);
+ },
+ [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg) {
+ nhlog::db()->debug("handle_room_verification: Start");
+ ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender);
+ },
+ [](const auto &) {},
+ },
+ event);
}
std::vector<mtx::events::collections::TimelineEvents>
EventStore::edits(const std::string &event_id)
{
- auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
-
- auto original_event = get(event_id, "", false, false);
- if (!original_event)
- return {};
-
- auto original_sender = mtx::accessors::sender(*original_event);
- auto original_relations = mtx::accessors::relations(*original_event);
-
- std::vector<mtx::events::collections::TimelineEvents> edits;
- for (const auto &id : event_ids) {
- auto related_event = get(id, event_id, false, false);
- if (!related_event)
- continue;
-
- auto related_ev = *related_event;
-
- auto edit_rel = mtx::accessors::relations(related_ev);
- if (edit_rel.replaces() == event_id &&
- original_sender == mtx::accessors::sender(related_ev)) {
- if (edit_rel.synthesized && original_relations.reply_to() &&
- !edit_rel.reply_to()) {
- edit_rel.relations.push_back(
- {mtx::common::RelationType::InReplyTo,
- original_relations.reply_to().value()});
- mtx::accessors::set_relations(related_ev, std::move(edit_rel));
- }
- edits.push_back(std::move(related_ev));
- }
+ auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
+
+ auto original_event = get(event_id, "", false, false);
+ if (!original_event)
+ return {};
+
+ auto original_sender = mtx::accessors::sender(*original_event);
+ auto original_relations = mtx::accessors::relations(*original_event);
+
+ std::vector<mtx::events::collections::TimelineEvents> edits;
+ for (const auto &id : event_ids) {
+ auto related_event = get(id, event_id, false, false);
+ if (!related_event)
+ continue;
+
+ auto related_ev = *related_event;
+
+ auto edit_rel = mtx::accessors::relations(related_ev);
+ if (edit_rel.replaces() == event_id &&
+ original_sender == mtx::accessors::sender(related_ev)) {
+ if (edit_rel.synthesized && original_relations.reply_to() && !edit_rel.reply_to()) {
+ edit_rel.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, original_relations.reply_to().value()});
+ mtx::accessors::set_relations(related_ev, std::move(edit_rel));
+ }
+ edits.push_back(std::move(related_ev));
}
-
- auto c = cache::client();
- std::sort(edits.begin(),
- edits.end(),
- [this, c](const mtx::events::collections::TimelineEvents &a,
- const mtx::events::collections::TimelineEvents &b) {
- return c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(a)) <
- c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(b));
- });
-
- return edits;
+ }
+
+ auto c = cache::client();
+ std::sort(edits.begin(),
+ edits.end(),
+ [this, c](const mtx::events::collections::TimelineEvents &a,
+ const mtx::events::collections::TimelineEvents &b) {
+ return c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(a)) <
+ c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(b));
+ });
+
+ return edits;
}
QVariantList
EventStore::reactions(const std::string &event_id)
{
- auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
-
- struct TempReaction
- {
- int count = 0;
- std::vector<std::string> users;
- std::string reactedBySelf;
- };
- std::map<std::string, TempReaction> aggregation;
- std::vector<Reaction> reactions;
-
- auto self = http::client()->user_id().to_string();
- for (const auto &id : event_ids) {
- auto related_event = get(id, event_id);
- if (!related_event)
- continue;
-
- if (auto reaction = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
- related_event);
- reaction && reaction->content.relations.annotates() &&
- reaction->content.relations.annotates()->key) {
- auto key = reaction->content.relations.annotates()->key.value();
- auto &agg = aggregation[key];
-
- if (agg.count == 0) {
- Reaction temp{};
- temp.key_ = QString::fromStdString(key);
- reactions.push_back(temp);
- }
-
- agg.count++;
- agg.users.push_back(cache::displayName(room_id_, reaction->sender));
- if (reaction->sender == self)
- agg.reactedBySelf = reaction->event_id;
- }
+ auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
+
+ struct TempReaction
+ {
+ int count = 0;
+ std::vector<std::string> users;
+ std::string reactedBySelf;
+ };
+ std::map<std::string, TempReaction> aggregation;
+ std::vector<Reaction> reactions;
+
+ auto self = http::client()->user_id().to_string();
+ for (const auto &id : event_ids) {
+ auto related_event = get(id, event_id);
+ if (!related_event)
+ continue;
+
+ if (auto reaction =
+ std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(related_event);
+ reaction && reaction->content.relations.annotates() &&
+ reaction->content.relations.annotates()->key) {
+ auto key = reaction->content.relations.annotates()->key.value();
+ auto &agg = aggregation[key];
+
+ if (agg.count == 0) {
+ Reaction temp{};
+ temp.key_ = QString::fromStdString(key);
+ reactions.push_back(temp);
+ }
+
+ agg.count++;
+ agg.users.push_back(cache::displayName(room_id_, reaction->sender));
+ if (reaction->sender == self)
+ agg.reactedBySelf = reaction->event_id;
}
-
- QVariantList temp;
- for (auto &reaction : reactions) {
- const auto &agg = aggregation[reaction.key_.toStdString()];
- reaction.count_ = agg.count;
- reaction.selfReactedEvent_ = QString::fromStdString(agg.reactedBySelf);
-
- bool firstReaction = true;
- for (const auto &user : agg.users) {
- if (firstReaction)
- firstReaction = false;
- else
- reaction.users_ += ", ";
-
- reaction.users_ += QString::fromStdString(user);
- }
-
- nhlog::db()->debug("key: {}, count: {}, users: {}",
- reaction.key_.toStdString(),
- reaction.count_,
- reaction.users_.toStdString());
- temp.append(QVariant::fromValue(reaction));
+ }
+
+ QVariantList temp;
+ for (auto &reaction : reactions) {
+ const auto &agg = aggregation[reaction.key_.toStdString()];
+ reaction.count_ = agg.count;
+ reaction.selfReactedEvent_ = QString::fromStdString(agg.reactedBySelf);
+
+ bool firstReaction = true;
+ for (const auto &user : agg.users) {
+ if (firstReaction)
+ firstReaction = false;
+ else
+ reaction.users_ += ", ";
+
+ reaction.users_ += QString::fromStdString(user);
}
- return temp;
+ nhlog::db()->debug("key: {}, count: {}, users: {}",
+ reaction.key_.toStdString(),
+ reaction.count_,
+ reaction.users_.toStdString());
+ temp.append(QVariant::fromValue(reaction));
+ }
+
+ return temp;
}
mtx::events::collections::TimelineEvents *
EventStore::get(int idx, bool decrypt)
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
-
- Index index{room_id_, toInternalIdx(idx)};
- if (index.idx > last || index.idx < first)
- return nullptr;
-
- auto event_ptr = events_.object(index);
- if (!event_ptr) {
- auto event_id = cache::client()->getTimelineEventId(room_id_, index.idx);
- if (!event_id)
- return nullptr;
-
- std::optional<mtx::events::collections::TimelineEvent> event;
- auto edits_ = edits(*event_id);
- if (edits_.empty())
- event = cache::client()->getEvent(room_id_, *event_id);
- else
- event = {edits_.back()};
-
- if (!event)
- return nullptr;
- else
- event_ptr =
- new mtx::events::collections::TimelineEvents(std::move(event->data));
- events_.insert(index, event_ptr);
- }
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ Index index{room_id_, toInternalIdx(idx)};
+ if (index.idx > last || index.idx < first)
+ return nullptr;
+
+ auto event_ptr = events_.object(index);
+ if (!event_ptr) {
+ auto event_id = cache::client()->getTimelineEventId(room_id_, index.idx);
+ if (!event_id)
+ return nullptr;
+
+ std::optional<mtx::events::collections::TimelineEvent> event;
+ auto edits_ = edits(*event_id);
+ if (edits_.empty())
+ event = cache::client()->getEvent(room_id_, *event_id);
+ else
+ event = {edits_.back()};
- if (decrypt) {
- if (auto encrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- event_ptr)) {
- auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
- if (decrypted->event)
- return &*decrypted->event;
- }
+ if (!event)
+ return nullptr;
+ else
+ event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
+ events_.insert(index, event_ptr);
+ }
+
+ if (decrypt) {
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
+ auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+ if (decrypted->event)
+ return &*decrypted->event;
}
+ }
- return event_ptr;
+ return event_ptr;
}
std::optional<int>
EventStore::idToIndex(std::string_view id) const
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
-
- auto idx = cache::client()->getTimelineIndex(room_id_, id);
- if (idx)
- return toExternalIdx(*idx);
- else
- return std::nullopt;
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ auto idx = cache::client()->getTimelineIndex(room_id_, id);
+ if (idx)
+ return toExternalIdx(*idx);
+ else
+ return std::nullopt;
}
std::optional<std::string>
EventStore::indexToId(int idx) const
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
- return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
+ return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
}
olm::DecryptionResult *
EventStore::decryptEvent(const IdIndex &idx,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
{
- if (auto cachedEvent = decryptedEvents_.object(idx))
- return cachedEvent;
-
- MegolmSessionIndex index(room_id_, e.content);
-
- auto asCacheEntry = [&idx](olm::DecryptionResult &&event) {
- auto event_ptr = new olm::DecryptionResult(std::move(event));
- decryptedEvents_.insert(idx, event_ptr);
- return event_ptr;
- };
-
- auto decryptionResult = olm::decryptEvent(index, e);
-
- if (decryptionResult.error) {
- switch (decryptionResult.error) {
- case olm::DecryptionErrorCode::MissingSession:
- case olm::DecryptionErrorCode::MissingSessionIndex: {
- nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
- index.room_id,
- index.session_id,
- e.sender);
-
- requestSession(e, false);
- break;
- }
- case olm::DecryptionErrorCode::DbError:
- nhlog::db()->critical(
- "failed to retrieve megolm session with index ({}, {}, {})",
- index.room_id,
- index.session_id,
- index.sender_key,
- decryptionResult.error_message.value_or(""));
- break;
- case olm::DecryptionErrorCode::DecryptionFailed:
- nhlog::crypto()->critical(
- "failed to decrypt message with index ({}, {}, {}): {}",
- index.room_id,
- index.session_id,
- index.sender_key,
- decryptionResult.error_message.value_or(""));
- break;
- case olm::DecryptionErrorCode::ParsingFailed:
- break;
- case olm::DecryptionErrorCode::ReplayAttack:
- nhlog::crypto()->critical(
- "Reply attack while decryptiong event {} in room {} from {}!",
- e.event_id,
- room_id_,
- index.sender_key);
- break;
- case olm::DecryptionErrorCode::NoError:
- // unreachable
- break;
- }
- return asCacheEntry(std::move(decryptionResult));
- }
+ if (auto cachedEvent = decryptedEvents_.object(idx))
+ return cachedEvent;
+
+ MegolmSessionIndex index(room_id_, e.content);
+
+ auto asCacheEntry = [&idx](olm::DecryptionResult &&event) {
+ auto event_ptr = new olm::DecryptionResult(std::move(event));
+ decryptedEvents_.insert(idx, event_ptr);
+ return event_ptr;
+ };
- auto encInfo = mtx::accessors::file(decryptionResult.event.value());
- if (encInfo)
- emit newEncryptedImage(encInfo.value());
+ auto decryptionResult = olm::decryptEvent(index, e);
+ if (decryptionResult.error) {
+ switch (decryptionResult.error) {
+ case olm::DecryptionErrorCode::MissingSession:
+ case olm::DecryptionErrorCode::MissingSessionIndex: {
+ nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
+ index.room_id,
+ index.session_id,
+ e.sender);
+
+ requestSession(e, false);
+ break;
+ }
+ case olm::DecryptionErrorCode::DbError:
+ nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ decryptionResult.error_message.value_or(""));
+ break;
+ case olm::DecryptionErrorCode::DecryptionFailed:
+ nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ decryptionResult.error_message.value_or(""));
+ break;
+ case olm::DecryptionErrorCode::ParsingFailed:
+ break;
+ case olm::DecryptionErrorCode::ReplayAttack:
+ nhlog::crypto()->critical("Reply attack while decryptiong event {} in room {} from {}!",
+ e.event_id,
+ room_id_,
+ index.sender_key);
+ break;
+ case olm::DecryptionErrorCode::NoError:
+ // unreachable
+ break;
+ }
return asCacheEntry(std::move(decryptionResult));
+ }
+
+ auto encInfo = mtx::accessors::file(decryptionResult.event.value());
+ if (encInfo)
+ emit newEncryptedImage(encInfo.value());
+
+ return asCacheEntry(std::move(decryptionResult));
}
void
EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
bool manual)
{
- // we may not want to request keys during initial sync and such
- if (suppressKeyRequests)
- return;
-
- // TODO: Look in key backup
- auto copy = ev;
- copy.room_id = room_id_;
- if (pending_key_requests.count(ev.content.session_id)) {
- auto &r = pending_key_requests.at(ev.content.session_id);
- r.events.push_back(copy);
-
- // automatically request once every 10 min, manually every 1 min
- qint64 delay = manual ? 60 : (60 * 10);
- if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) {
- r.requested_at = QDateTime::currentSecsSinceEpoch();
- olm::lookup_keybackup(room_id_, ev.content.session_id);
- olm::send_key_request_for(copy, r.request_id);
- }
- } else {
- PendingKeyRequests request;
- request.request_id = "key_request." + http::client()->generate_txn_id();
- request.requested_at = QDateTime::currentSecsSinceEpoch();
- request.events.push_back(copy);
- olm::lookup_keybackup(room_id_, ev.content.session_id);
- olm::send_key_request_for(copy, request.request_id);
- pending_key_requests[ev.content.session_id] = request;
+ // we may not want to request keys during initial sync and such
+ if (suppressKeyRequests)
+ return;
+
+ // TODO: Look in key backup
+ auto copy = ev;
+ copy.room_id = room_id_;
+ if (pending_key_requests.count(ev.content.session_id)) {
+ auto &r = pending_key_requests.at(ev.content.session_id);
+ r.events.push_back(copy);
+
+ // automatically request once every 10 min, manually every 1 min
+ qint64 delay = manual ? 60 : (60 * 10);
+ if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) {
+ r.requested_at = QDateTime::currentSecsSinceEpoch();
+ olm::lookup_keybackup(room_id_, ev.content.session_id);
+ olm::send_key_request_for(copy, r.request_id);
}
+ } else {
+ PendingKeyRequests request;
+ request.request_id = "key_request." + http::client()->generate_txn_id();
+ request.requested_at = QDateTime::currentSecsSinceEpoch();
+ request.events.push_back(copy);
+ olm::lookup_keybackup(room_id_, ev.content.session_id);
+ olm::send_key_request_for(copy, request.request_id);
+ pending_key_requests[ev.content.session_id] = request;
+ }
}
void
EventStore::enableKeyRequests(bool suppressKeyRequests_)
{
- if (!suppressKeyRequests_) {
- for (const auto &key : decryptedEvents_.keys())
- if (key.room == this->room_id_)
- decryptedEvents_.remove(key);
- suppressKeyRequests = false;
- } else
- suppressKeyRequests = true;
+ if (!suppressKeyRequests_) {
+ for (const auto &key : decryptedEvents_.keys())
+ if (key.room == this->room_id_)
+ decryptedEvents_.remove(key);
+ suppressKeyRequests = false;
+ } else
+ suppressKeyRequests = true;
}
mtx::events::collections::TimelineEvents *
EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool resolve_edits)
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
-
- if (id.empty())
- return nullptr;
-
- IdIndex index{room_id_, std::move(id)};
- if (resolve_edits) {
- auto edits_ = edits(index.id);
- if (!edits_.empty()) {
- index.id = mtx::accessors::event_id(edits_.back());
- auto event_ptr =
- new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
- events_by_id_.insert(index, event_ptr);
- }
- }
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
- auto event_ptr = events_by_id_.object(index);
- if (!event_ptr) {
- auto event = cache::client()->getEvent(room_id_, index.id);
- if (!event) {
- http::client()->get_event(
- room_id_,
- index.id,
- [this, relatedTo = std::string(related_to), id = index.id](
- const mtx::events::collections::TimelineEvents &timeline,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->error(
- "Failed to retrieve event with id {}, which was "
- "requested to show the replyTo for event {}",
- relatedTo,
- id);
- return;
- }
- emit eventFetched(id, relatedTo, timeline);
- });
- return nullptr;
- }
- event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
- events_by_id_.insert(index, event_ptr);
+ if (id.empty())
+ return nullptr;
+
+ IdIndex index{room_id_, std::move(id)};
+ if (resolve_edits) {
+ auto edits_ = edits(index.id);
+ if (!edits_.empty()) {
+ index.id = mtx::accessors::event_id(edits_.back());
+ auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
+ events_by_id_.insert(index, event_ptr);
}
+ }
+
+ auto event_ptr = events_by_id_.object(index);
+ if (!event_ptr) {
+ auto event = cache::client()->getEvent(room_id_, index.id);
+ if (!event) {
+ http::client()->get_event(room_id_,
+ index.id,
+ [this, relatedTo = std::string(related_to), id = index.id](
+ const mtx::events::collections::TimelineEvents &timeline,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->error(
+ "Failed to retrieve event with id {}, which was "
+ "requested to show the replyTo for event {}",
+ relatedTo,
+ id);
+ return;
+ }
+ emit eventFetched(id, relatedTo, timeline);
+ });
+ return nullptr;
+ }
+ event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
+ events_by_id_.insert(index, event_ptr);
+ }
- if (decrypt) {
- if (auto encrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- event_ptr)) {
- auto decrypted = decryptEvent(index, *encrypted);
- if (decrypted->event)
- return &*decrypted->event;
- }
+ if (decrypt) {
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
+ auto decrypted = decryptEvent(index, *encrypted);
+ if (decrypted->event)
+ return &*decrypted->event;
}
+ }
- return event_ptr;
+ return event_ptr;
}
olm::DecryptionErrorCode
EventStore::decryptionError(std::string id)
{
- if (this->thread() != QThread::currentThread())
- nhlog::db()->warn("{} called from a different thread!", __func__);
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
- if (id.empty())
- return olm::DecryptionErrorCode::NoError;
-
- IdIndex index{room_id_, std::move(id)};
- auto edits_ = edits(index.id);
- if (!edits_.empty()) {
- index.id = mtx::accessors::event_id(edits_.back());
- auto event_ptr =
- new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
- events_by_id_.insert(index, event_ptr);
- }
+ if (id.empty())
+ return olm::DecryptionErrorCode::NoError;
- auto event_ptr = events_by_id_.object(index);
- if (!event_ptr) {
- auto event = cache::client()->getEvent(room_id_, index.id);
- if (!event) {
- return olm::DecryptionErrorCode::NoError;
- }
- event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
- events_by_id_.insert(index, event_ptr);
+ IdIndex index{room_id_, std::move(id)};
+ auto edits_ = edits(index.id);
+ if (!edits_.empty()) {
+ index.id = mtx::accessors::event_id(edits_.back());
+ auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
+ events_by_id_.insert(index, event_ptr);
+ }
+
+ auto event_ptr = events_by_id_.object(index);
+ if (!event_ptr) {
+ auto event = cache::client()->getEvent(room_id_, index.id);
+ if (!event) {
+ return olm::DecryptionErrorCode::NoError;
}
+ event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
+ events_by_id_.insert(index, event_ptr);
+ }
- if (auto encrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
- auto decrypted = decryptEvent(index, *encrypted);
- return decrypted->error;
- }
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
+ auto decrypted = decryptEvent(index, *encrypted);
+ return decrypted->error;
+ }
- return olm::DecryptionErrorCode::NoError;
+ return olm::DecryptionErrorCode::NoError;
}
void
EventStore::fetchMore()
{
- if (noMoreMessages)
- return;
-
- mtx::http::MessagesOpts opts;
- opts.room_id = room_id_;
- opts.from = cache::client()->previousBatchToken(room_id_);
-
- nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from);
-
- http::client()->messages(
- opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) {
- if (cache::client()->previousBatchToken(room_id_) != opts.from) {
- nhlog::net()->warn("Cache cleared while fetching more messages, dropping "
- "/messages response");
- if (!opts.to.empty())
- emit fetchedMore();
- return;
- }
- if (err) {
- nhlog::net()->error("failed to call /messages ({}): {} - {} - {}",
- opts.room_id,
- mtx::errors::to_string(err->matrix_error.errcode),
- err->matrix_error.error,
- err->parse_error);
- emit fetchedMore();
- return;
- }
-
- emit oldMessagesRetrieved(std::move(res));
- });
+ if (noMoreMessages)
+ return;
+
+ mtx::http::MessagesOpts opts;
+ opts.room_id = room_id_;
+ opts.from = cache::client()->previousBatchToken(room_id_);
+
+ nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from);
+
+ http::client()->messages(
+ opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) {
+ if (cache::client()->previousBatchToken(room_id_) != opts.from) {
+ nhlog::net()->warn("Cache cleared while fetching more messages, dropping "
+ "/messages response");
+ if (!opts.to.empty())
+ emit fetchedMore();
+ return;
+ }
+ if (err) {
+ nhlog::net()->error("failed to call /messages ({}): {} - {} - {}",
+ opts.room_id,
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error,
+ err->parse_error);
+ emit fetchedMore();
+ return;
+ }
+
+ emit oldMessagesRetrieved(std::move(res));
+ });
}
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 59c1c7c0..53dbaff4 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -20,133 +20,131 @@
class EventStore : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
- EventStore(std::string room_id, QObject *parent);
-
- // taken from QtPrivate::QHashCombine
- static uint hashCombine(uint hash, uint seed)
- {
- return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2));
- };
- struct Index
+ EventStore(std::string room_id, QObject *parent);
+
+ // taken from QtPrivate::QHashCombine
+ static uint hashCombine(uint hash, uint seed)
+ {
+ return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+ };
+ struct Index
+ {
+ std::string room;
+ uint64_t idx;
+
+ friend uint qHash(const Index &i, uint seed = 0) noexcept
{
- std::string room;
- uint64_t idx;
-
- friend uint qHash(const Index &i, uint seed = 0) noexcept
- {
- seed =
- hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed);
- seed = hashCombine(qHash(i.idx, seed), seed);
- return seed;
- }
-
- friend bool operator==(const Index &a, const Index &b) noexcept
- {
- return a.idx == b.idx && a.room == b.room;
- }
- };
- struct IdIndex
+ seed = hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed);
+ seed = hashCombine(qHash(i.idx, seed), seed);
+ return seed;
+ }
+
+ friend bool operator==(const Index &a, const Index &b) noexcept
{
- std::string room, id;
-
- friend uint qHash(const IdIndex &i, uint seed = 0) noexcept
- {
- seed =
- hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed);
- seed = hashCombine(qHashBits(i.id.data(), (int)i.id.size(), seed), seed);
- return seed;
- }
-
- friend bool operator==(const IdIndex &a, const IdIndex &b) noexcept
- {
- return a.id == b.id && a.room == b.room;
- }
- };
-
- void fetchMore();
- void handleSync(const mtx::responses::Timeline &events);
-
- // optionally returns the event or nullptr and fetches it, after which it emits a
- // relatedFetched event
- mtx::events::collections::TimelineEvents *get(std::string id,
- std::string_view related_to,
- bool decrypt = true,
- bool resolve_edits = true);
- // always returns a proper event as long as the idx is valid
- mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
-
- QVariantList reactions(const std::string &event_id);
- olm::DecryptionErrorCode decryptionError(std::string id);
- void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
- bool manual);
-
- int size() const
+ return a.idx == b.idx && a.room == b.room;
+ }
+ };
+ struct IdIndex
+ {
+ std::string room, id;
+
+ friend uint qHash(const IdIndex &i, uint seed = 0) noexcept
{
- return (last != std::numeric_limits<uint64_t>::max() && last >= first)
- ? static_cast<int>(last - first) + 1
- : 0;
+ seed = hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed);
+ seed = hashCombine(qHashBits(i.id.data(), (int)i.id.size(), seed), seed);
+ return seed;
}
- int toExternalIdx(uint64_t idx) const { return static_cast<int>(idx - first); }
- uint64_t toInternalIdx(int idx) const { return first + idx; }
- std::optional<int> idToIndex(std::string_view id) const;
- std::optional<std::string> indexToId(int idx) const;
+ friend bool operator==(const IdIndex &a, const IdIndex &b) noexcept
+ {
+ return a.id == b.id && a.room == b.room;
+ }
+ };
+
+ void fetchMore();
+ void handleSync(const mtx::responses::Timeline &events);
+
+ // optionally returns the event or nullptr and fetches it, after which it emits a
+ // relatedFetched event
+ mtx::events::collections::TimelineEvents *get(std::string id,
+ std::string_view related_to,
+ bool decrypt = true,
+ bool resolve_edits = true);
+ // always returns a proper event as long as the idx is valid
+ mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
+
+ QVariantList reactions(const std::string &event_id);
+ olm::DecryptionErrorCode decryptionError(std::string id);
+ void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
+ bool manual);
+
+ int size() const
+ {
+ return (last != std::numeric_limits<uint64_t>::max() && last >= first)
+ ? static_cast<int>(last - first) + 1
+ : 0;
+ }
+ int toExternalIdx(uint64_t idx) const { return static_cast<int>(idx - first); }
+ uint64_t toInternalIdx(int idx) const { return first + idx; }
+
+ std::optional<int> idToIndex(std::string_view id) const;
+ std::optional<std::string> indexToId(int idx) const;
signals:
- void beginInsertRows(int from, int to);
- void endInsertRows();
- void beginResetModel();
- void endResetModel();
- void dataChanged(int from, int to);
- void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
- void eventFetched(std::string id,
- std::string relatedTo,
- mtx::events::collections::TimelineEvents timeline);
- void oldMessagesRetrieved(const mtx::responses::Messages &);
- void fetchedMore();
-
- void processPending();
- void messageSent(std::string txn_id, std::string event_id);
- void messageFailed(std::string txn_id);
- void startDMVerification(
- const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg);
- void updateFlowEventId(std::string event_id);
+ void beginInsertRows(int from, int to);
+ void endInsertRows();
+ void beginResetModel();
+ void endResetModel();
+ void dataChanged(int from, int to);
+ void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
+ void eventFetched(std::string id,
+ std::string relatedTo,
+ mtx::events::collections::TimelineEvents timeline);
+ void oldMessagesRetrieved(const mtx::responses::Messages &);
+ void fetchedMore();
+
+ void processPending();
+ void messageSent(std::string txn_id, std::string event_id);
+ void messageFailed(std::string txn_id);
+ void startDMVerification(
+ const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg);
+ void updateFlowEventId(std::string event_id);
public slots:
- void addPending(mtx::events::collections::TimelineEvents event);
- void receivedSessionKey(const std::string &session_id);
- void clearTimeline();
- void enableKeyRequests(bool suppressKeyRequests_);
+ void addPending(mtx::events::collections::TimelineEvents event);
+ void receivedSessionKey(const std::string &session_id);
+ void clearTimeline();
+ void enableKeyRequests(bool suppressKeyRequests_);
private:
- std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
- olm::DecryptionResult *decryptEvent(
- const IdIndex &idx,
- const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
- void handle_room_verification(mtx::events::collections::TimelineEvents event);
-
- std::string room_id_;
-
- uint64_t first = std::numeric_limits<uint64_t>::max(),
- last = std::numeric_limits<uint64_t>::max();
-
- static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_;
- static QCache<Index, mtx::events::collections::TimelineEvents> events_;
- static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
-
- struct PendingKeyRequests
- {
- std::string request_id;
- std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events;
- qint64 requested_at;
- };
- std::map<std::string, PendingKeyRequests> pending_key_requests;
-
- std::string current_txn;
- int current_txn_error_count = 0;
- bool noMoreMessages = false;
- bool suppressKeyRequests = true;
+ std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
+ olm::DecryptionResult *decryptEvent(
+ const IdIndex &idx,
+ const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
+ void handle_room_verification(mtx::events::collections::TimelineEvents event);
+
+ std::string room_id_;
+
+ uint64_t first = std::numeric_limits<uint64_t>::max(),
+ last = std::numeric_limits<uint64_t>::max();
+
+ static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_;
+ static QCache<Index, mtx::events::collections::TimelineEvents> events_;
+ static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
+
+ struct PendingKeyRequests
+ {
+ std::string request_id;
+ std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events;
+ qint64 requested_at;
+ };
+ std::map<std::string, PendingKeyRequests> pending_key_requests;
+
+ std::string current_txn;
+ int current_txn_error_count = 0;
+ bool noMoreMessages = false;
+ bool suppressKeyRequests = true;
};
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index a6fbab78..f0c38c84 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -43,374 +43,370 @@ static constexpr size_t INPUT_HISTORY_SIZE = 10;
void
InputBar::paste(bool fromMouse)
{
- const QMimeData *md = nullptr;
+ const QMimeData *md = nullptr;
- if (fromMouse) {
- if (QGuiApplication::clipboard()->supportsSelection()) {
- md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
- }
- } else {
- md = QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard);
+ if (fromMouse) {
+ if (QGuiApplication::clipboard()->supportsSelection()) {
+ md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
}
+ } else {
+ md = QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard);
+ }
- if (md)
- insertMimeData(md);
+ if (md)
+ insertMimeData(md);
}
void
InputBar::insertMimeData(const QMimeData *md)
{
- if (!md)
- return;
-
- nhlog::ui()->debug("Got mime formats: {}", md->formats().join(", ").toStdString());
- const auto formats = md->formats().filter("/");
- const auto image = formats.filter("image/", Qt::CaseInsensitive);
- const auto audio = formats.filter("audio/", Qt::CaseInsensitive);
- const auto video = formats.filter("video/", Qt::CaseInsensitive);
-
- if (!image.empty() && md->hasImage()) {
- showPreview(*md, "", image);
- } else if (!audio.empty()) {
- showPreview(*md, "", audio);
- } else if (!video.empty()) {
- showPreview(*md, "", video);
- } else if (md->hasUrls()) {
- // Generic file path for any platform.
- QString path;
- for (auto &&u : md->urls()) {
- if (u.isLocalFile()) {
- path = u.toLocalFile();
- break;
- }
- }
+ if (!md)
+ return;
+
+ nhlog::ui()->debug("Got mime formats: {}", md->formats().join(", ").toStdString());
+ const auto formats = md->formats().filter("/");
+ const auto image = formats.filter("image/", Qt::CaseInsensitive);
+ const auto audio = formats.filter("audio/", Qt::CaseInsensitive);
+ const auto video = formats.filter("video/", Qt::CaseInsensitive);
+
+ if (!image.empty() && md->hasImage()) {
+ showPreview(*md, "", image);
+ } else if (!audio.empty()) {
+ showPreview(*md, "", audio);
+ } else if (!video.empty()) {
+ showPreview(*md, "", video);
+ } else if (md->hasUrls()) {
+ // Generic file path for any platform.
+ QString path;
+ for (auto &&u : md->urls()) {
+ if (u.isLocalFile()) {
+ path = u.toLocalFile();
+ break;
+ }
+ }
- if (!path.isEmpty() && QFileInfo{path}.exists()) {
- showPreview(*md, path, formats);
- } else {
- nhlog::ui()->warn("Clipboard does not contain any valid file paths.");
- }
- } else if (md->hasFormat("x-special/gnome-copied-files")) {
- // Special case for X11 users. See "Notes for X11 Users" in md.
- // Source: http://doc.qt.io/qt-5/qclipboard.html
-
- // This MIME type returns a string with multiple lines separated by '\n'. The first
- // line is the command to perform with the clipboard (not useful to us). The
- // following lines are the file URIs.
- //
- // Source: the nautilus source code in file 'src/nautilus-clipboard.c' in function
- // nautilus_clipboard_get_uri_list_from_selection_data()
- // https://github.com/GNOME/nautilus/blob/master/src/nautilus-clipboard.c
-
- auto data = md->data("x-special/gnome-copied-files").split('\n');
- if (data.size() < 2) {
- nhlog::ui()->warn("MIME format is malformed, cannot perform paste.");
- return;
- }
+ if (!path.isEmpty() && QFileInfo{path}.exists()) {
+ showPreview(*md, path, formats);
+ } else {
+ nhlog::ui()->warn("Clipboard does not contain any valid file paths.");
+ }
+ } else if (md->hasFormat("x-special/gnome-copied-files")) {
+ // Special case for X11 users. See "Notes for X11 Users" in md.
+ // Source: http://doc.qt.io/qt-5/qclipboard.html
+
+ // This MIME type returns a string with multiple lines separated by '\n'. The first
+ // line is the command to perform with the clipboard (not useful to us). The
+ // following lines are the file URIs.
+ //
+ // Source: the nautilus source code in file 'src/nautilus-clipboard.c' in function
+ // nautilus_clipboard_get_uri_list_from_selection_data()
+ // https://github.com/GNOME/nautilus/blob/master/src/nautilus-clipboard.c
+
+ auto data = md->data("x-special/gnome-copied-files").split('\n');
+ if (data.size() < 2) {
+ nhlog::ui()->warn("MIME format is malformed, cannot perform paste.");
+ return;
+ }
- QString path;
- for (int i = 1; i < data.size(); ++i) {
- QUrl url{data[i]};
- if (url.isLocalFile()) {
- path = url.toLocalFile();
- break;
- }
- }
+ QString path;
+ for (int i = 1; i < data.size(); ++i) {
+ QUrl url{data[i]};
+ if (url.isLocalFile()) {
+ path = url.toLocalFile();
+ break;
+ }
+ }
- if (!path.isEmpty()) {
- showPreview(*md, path, formats);
- } else {
- nhlog::ui()->warn("Clipboard does not contain any valid file paths: {}",
- data.join(", ").toStdString());
- }
- } else if (md->hasText()) {
- emit insertText(md->text());
+ if (!path.isEmpty()) {
+ showPreview(*md, path, formats);
} else {
- nhlog::ui()->debug("formats: {}", md->formats().join(", ").toStdString());
+ nhlog::ui()->warn("Clipboard does not contain any valid file paths: {}",
+ data.join(", ").toStdString());
}
+ } else if (md->hasText()) {
+ emit insertText(md->text());
+ } else {
+ nhlog::ui()->debug("formats: {}", md->formats().join(", ").toStdString());
+ }
}
void
InputBar::updateAtRoom(const QString &t)
{
- bool roomMention = false;
-
- if (t.size() > 4) {
- QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t);
-
- finder.toStart();
- do {
- auto start = finder.position();
- finder.toNextBoundary();
- auto end = finder.position();
- if (start > 0 && end - start >= 4 &&
- t.midRef(start, end - start) == "room" &&
- t.at(start - 1) == QChar('@')) {
- roomMention = true;
- break;
- }
- } while (finder.position() < t.size());
- }
-
- if (roomMention != this->containsAtRoom_) {
- this->containsAtRoom_ = roomMention;
- emit containsAtRoomChanged();
- }
+ bool roomMention = false;
+
+ if (t.size() > 4) {
+ QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t);
+
+ finder.toStart();
+ do {
+ auto start = finder.position();
+ finder.toNextBoundary();
+ auto end = finder.position();
+ if (start > 0 && end - start >= 4 && t.midRef(start, end - start) == "room" &&
+ t.at(start - 1) == QChar('@')) {
+ roomMention = true;
+ break;
+ }
+ } while (finder.position() < t.size());
+ }
+
+ if (roomMention != this->containsAtRoom_) {
+ this->containsAtRoom_ = roomMention;
+ emit containsAtRoomChanged();
+ }
}
void
InputBar::setText(QString newText)
{
- if (history_.empty())
- history_.push_front(newText);
- else
- history_.front() = newText;
- history_index_ = 0;
+ if (history_.empty())
+ history_.push_front(newText);
+ else
+ history_.front() = newText;
+ history_index_ = 0;
- if (history_.size() == INPUT_HISTORY_SIZE)
- history_.pop_back();
+ if (history_.size() == INPUT_HISTORY_SIZE)
+ history_.pop_back();
- emit textChanged(newText);
+ emit textChanged(newText);
}
void
InputBar::updateState(int selectionStart_, int selectionEnd_, int cursorPosition_, QString text_)
{
- if (text_.isEmpty())
- stopTyping();
- else
- startTyping();
+ if (text_.isEmpty())
+ stopTyping();
+ else
+ startTyping();
- if (text_ != text()) {
- if (history_.empty())
- history_.push_front(text_);
- else
- history_.front() = text_;
- history_index_ = 0;
+ if (text_ != text()) {
+ if (history_.empty())
+ history_.push_front(text_);
+ else
+ history_.front() = text_;
+ history_index_ = 0;
- updateAtRoom(text_);
- }
+ updateAtRoom(text_);
+ }
- selectionStart = selectionStart_;
- selectionEnd = selectionEnd_;
- cursorPosition = cursorPosition_;
+ selectionStart = selectionStart_;
+ selectionEnd = selectionEnd_;
+ cursorPosition = cursorPosition_;
}
QString
InputBar::text() const
{
- if (history_index_ < history_.size())
- return history_.at(history_index_);
+ if (history_index_ < history_.size())
+ return history_.at(history_index_);
- return "";
+ return "";
}
QString
InputBar::previousText()
{
- history_index_++;
- if (history_index_ >= INPUT_HISTORY_SIZE)
- history_index_ = INPUT_HISTORY_SIZE;
- else if (text().isEmpty())
- history_index_--;
-
- updateAtRoom(text());
- return text();
+ history_index_++;
+ if (history_index_ >= INPUT_HISTORY_SIZE)
+ history_index_ = INPUT_HISTORY_SIZE;
+ else if (text().isEmpty())
+ history_index_--;
+
+ updateAtRoom(text());
+ return text();
}
QString
InputBar::nextText()
{
- history_index_--;
- if (history_index_ >= INPUT_HISTORY_SIZE)
- history_index_ = 0;
+ history_index_--;
+ if (history_index_ >= INPUT_HISTORY_SIZE)
+ history_index_ = 0;
- updateAtRoom(text());
- return text();
+ updateAtRoom(text());
+ return text();
}
void
InputBar::send()
{
- if (text().trimmed().isEmpty())
- return;
-
- nhlog::ui()->debug("Send: {}", text().toStdString());
-
- auto wasEdit = !room->edit().isEmpty();
-
- if (text().startsWith('/')) {
- int command_end = text().indexOf(QRegularExpression("\\s"));
- if (command_end == -1)
- command_end = text().size();
- auto name = text().mid(1, command_end - 1);
- auto args = text().mid(command_end + 1);
- if (name.isEmpty() || name == "/") {
- message(args);
- } else {
- command(name, args);
- }
- } else {
- message(text());
- }
+ if (text().trimmed().isEmpty())
+ return;
- if (!wasEdit) {
- history_.push_front("");
- setText("");
+ nhlog::ui()->debug("Send: {}", text().toStdString());
+
+ auto wasEdit = !room->edit().isEmpty();
+
+ if (text().startsWith('/')) {
+ int command_end = text().indexOf(QRegularExpression("\\s"));
+ if (command_end == -1)
+ command_end = text().size();
+ auto name = text().mid(1, command_end - 1);
+ auto args = text().mid(command_end + 1);
+ if (name.isEmpty() || name == "/") {
+ message(args);
+ } else {
+ command(name, args);
}
+ } else {
+ message(text());
+ }
+
+ if (!wasEdit) {
+ history_.push_front("");
+ setText("");
+ }
}
void
InputBar::openFileSelection()
{
- const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
- const auto fileName = QFileDialog::getOpenFileName(
- ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
+ const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+ const auto fileName = QFileDialog::getOpenFileName(
+ ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
- if (fileName.isEmpty())
- return;
+ if (fileName.isEmpty())
+ return;
- QMimeDatabase db;
- QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
- QFile file{fileName};
+ QFile file{fileName};
- if (!file.open(QIODevice::ReadOnly)) {
- emit ChatPage::instance()->showNotification(
- QString("Error while reading media: %1").arg(file.errorString()));
- return;
- }
+ if (!file.open(QIODevice::ReadOnly)) {
+ emit ChatPage::instance()->showNotification(
+ QString("Error while reading media: %1").arg(file.errorString()));
+ return;
+ }
- setUploading(true);
+ setUploading(true);
- auto bin = file.readAll();
+ auto bin = file.readAll();
- QMimeData data;
- data.setData(mime.name(), bin);
+ QMimeData data;
+ data.setData(mime.name(), bin);
- showPreview(data, fileName, QStringList{mime.name()});
+ showPreview(data, fileName, QStringList{mime.name()});
}
void
InputBar::message(QString msg, MarkdownOverride useMarkdown, bool rainbowify)
{
- mtx::events::msg::Text text = {};
- text.body = msg.trimmed().toStdString();
+ mtx::events::msg::Text text = {};
+ text.body = msg.trimmed().toStdString();
+
+ if ((ChatPage::instance()->userSettings()->markdown() &&
+ useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
+ useMarkdown == MarkdownOverride::ON) {
+ text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString();
+ // Remove markdown links by completer
+ text.body = msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
+
+ // Don't send formatted_body, when we don't need to
+ if (text.formatted_body.find("<") == std::string::npos)
+ text.formatted_body = "";
+ else
+ text.format = "org.matrix.custom.html";
+ }
- if ((ChatPage::instance()->userSettings()->markdown() &&
- useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
- useMarkdown == MarkdownOverride::ON) {
- text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString();
- // Remove markdown links by completer
- text.body =
- msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
-
- // Don't send formatted_body, when we don't need to
- if (text.formatted_body.find("<") == std::string::npos)
- text.formatted_body = "";
- else
- text.format = "org.matrix.custom.html";
+ if (!room->edit().isEmpty()) {
+ if (!room->reply().isEmpty()) {
+ text.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
}
- if (!room->edit().isEmpty()) {
- if (!room->reply().isEmpty()) {
- text.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
-
- text.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
-
- } else if (!room->reply().isEmpty()) {
- auto related = room->relatedInfo(room->reply());
-
- QString body;
- bool firstLine = true;
- for (const auto &line : related.quoted_body.split("\n")) {
- if (firstLine) {
- firstLine = false;
- body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line);
- } else {
- body += QString("> %1\n").arg(line);
- }
- }
+ text.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+
+ } else if (!room->reply().isEmpty()) {
+ auto related = room->relatedInfo(room->reply());
+
+ QString body;
+ bool firstLine = true;
+ for (const auto &line : related.quoted_body.split("\n")) {
+ if (firstLine) {
+ firstLine = false;
+ body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line);
+ } else {
+ body += QString("> %1\n").arg(line);
+ }
+ }
- text.body = QString("%1\n%2").arg(body).arg(msg).toStdString();
+ text.body = QString("%1\n%2").arg(body).arg(msg).toStdString();
- // NOTE(Nico): rich replies always need a formatted_body!
- text.format = "org.matrix.custom.html";
- if ((ChatPage::instance()->userSettings()->markdown() &&
- useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
- useMarkdown == MarkdownOverride::ON)
- text.formatted_body = utils::getFormattedQuoteBody(
- related, utils::markdownToHtml(msg, rainbowify))
- .toStdString();
- else
- text.formatted_body =
- utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
+ // NOTE(Nico): rich replies always need a formatted_body!
+ text.format = "org.matrix.custom.html";
+ if ((ChatPage::instance()->userSettings()->markdown() &&
+ useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
+ useMarkdown == MarkdownOverride::ON)
+ text.formatted_body =
+ utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg, rainbowify))
+ .toStdString();
+ else
+ text.formatted_body =
+ utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
- text.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, related.related_event});
- }
+ text.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, related.related_event});
+ }
- room->sendMessageEvent(text, mtx::events::EventType::RoomMessage);
+ room->sendMessageEvent(text, mtx::events::EventType::RoomMessage);
}
void
InputBar::emote(QString msg, bool rainbowify)
{
- auto html = utils::markdownToHtml(msg, rainbowify);
-
- mtx::events::msg::Emote emote;
- emote.body = msg.trimmed().toStdString();
-
- if (html != msg.trimmed().toHtmlEscaped() &&
- ChatPage::instance()->userSettings()->markdown()) {
- emote.formatted_body = html.toStdString();
- emote.format = "org.matrix.custom.html";
- // Remove markdown links by completer
- emote.body =
- msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
- }
-
- if (!room->reply().isEmpty()) {
- emote.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- emote.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
+ auto html = utils::markdownToHtml(msg, rainbowify);
+
+ mtx::events::msg::Emote emote;
+ emote.body = msg.trimmed().toStdString();
+
+ if (html != msg.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) {
+ emote.formatted_body = html.toStdString();
+ emote.format = "org.matrix.custom.html";
+ // Remove markdown links by completer
+ emote.body =
+ msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
+ }
+
+ if (!room->reply().isEmpty()) {
+ emote.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ emote.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
}
void
InputBar::notice(QString msg, bool rainbowify)
{
- auto html = utils::markdownToHtml(msg, rainbowify);
-
- mtx::events::msg::Notice notice;
- notice.body = msg.trimmed().toStdString();
-
- if (html != msg.trimmed().toHtmlEscaped() &&
- ChatPage::instance()->userSettings()->markdown()) {
- notice.formatted_body = html.toStdString();
- notice.format = "org.matrix.custom.html";
- // Remove markdown links by completer
- notice.body =
- msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
- }
-
- if (!room->reply().isEmpty()) {
- notice.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- notice.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage);
+ auto html = utils::markdownToHtml(msg, rainbowify);
+
+ mtx::events::msg::Notice notice;
+ notice.body = msg.trimmed().toStdString();
+
+ if (html != msg.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) {
+ notice.formatted_body = html.toStdString();
+ notice.format = "org.matrix.custom.html";
+ // Remove markdown links by completer
+ notice.body =
+ msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
+ }
+
+ if (!room->reply().isEmpty()) {
+ notice.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ notice.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage);
}
void
@@ -422,29 +418,29 @@ InputBar::image(const QString &filename,
const QSize &dimensions,
const QString &blurhash)
{
- mtx::events::msg::Image image;
- image.info.mimetype = mime.toStdString();
- image.info.size = dsize;
- image.info.blurhash = blurhash.toStdString();
- image.body = filename.toStdString();
- image.info.h = dimensions.height();
- image.info.w = dimensions.width();
-
- if (file)
- image.file = file;
- else
- image.url = url.toStdString();
-
- if (!room->reply().isEmpty()) {
- image.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- image.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
+ mtx::events::msg::Image image;
+ image.info.mimetype = mime.toStdString();
+ image.info.size = dsize;
+ image.info.blurhash = blurhash.toStdString();
+ image.body = filename.toStdString();
+ image.info.h = dimensions.height();
+ image.info.w = dimensions.width();
+
+ if (file)
+ image.file = file;
+ else
+ image.url = url.toStdString();
+
+ if (!room->reply().isEmpty()) {
+ image.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ image.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
}
void
@@ -454,26 +450,26 @@ InputBar::file(const QString &filename,
const QString &mime,
uint64_t dsize)
{
- mtx::events::msg::File file;
- file.info.mimetype = mime.toStdString();
- file.info.size = dsize;
- file.body = filename.toStdString();
-
- if (encryptedFile)
- file.file = encryptedFile;
- else
- file.url = url.toStdString();
-
- if (!room->reply().isEmpty()) {
- file.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- file.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
+ mtx::events::msg::File file;
+ file.info.mimetype = mime.toStdString();
+ file.info.size = dsize;
+ file.body = filename.toStdString();
+
+ if (encryptedFile)
+ file.file = encryptedFile;
+ else
+ file.url = url.toStdString();
+
+ if (!room->reply().isEmpty()) {
+ file.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ file.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
}
void
@@ -483,27 +479,27 @@ InputBar::audio(const QString &filename,
const QString &mime,
uint64_t dsize)
{
- mtx::events::msg::Audio audio;
- audio.info.mimetype = mime.toStdString();
- audio.info.size = dsize;
- audio.body = filename.toStdString();
- audio.url = url.toStdString();
-
- if (file)
- audio.file = file;
- else
- audio.url = url.toStdString();
-
- if (!room->reply().isEmpty()) {
- audio.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- audio.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
+ mtx::events::msg::Audio audio;
+ audio.info.mimetype = mime.toStdString();
+ audio.info.size = dsize;
+ audio.body = filename.toStdString();
+ audio.url = url.toStdString();
+
+ if (file)
+ audio.file = file;
+ else
+ audio.url = url.toStdString();
+
+ if (!room->reply().isEmpty()) {
+ audio.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ audio.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
}
void
@@ -513,320 +509,310 @@ InputBar::video(const QString &filename,
const QString &mime,
uint64_t dsize)
{
- mtx::events::msg::Video video;
- video.info.mimetype = mime.toStdString();
- video.info.size = dsize;
- video.body = filename.toStdString();
-
- if (file)
- video.file = file;
- else
- video.url = url.toStdString();
-
- if (!room->reply().isEmpty()) {
- video.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- video.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
+ mtx::events::msg::Video video;
+ video.info.mimetype = mime.toStdString();
+ video.info.size = dsize;
+ video.body = filename.toStdString();
+
+ if (file)
+ video.file = file;
+ else
+ video.url = url.toStdString();
+
+ if (!room->reply().isEmpty()) {
+ video.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ video.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
}
void
InputBar::sticker(CombinedImagePackModel *model, int row)
{
- if (!model || row < 0)
- return;
-
- auto img = model->imageAt(row);
-
- mtx::events::msg::StickerImage sticker{};
- sticker.info = img.info.value_or(mtx::common::ImageInfo{});
- sticker.url = img.url;
- sticker.body = img.body;
-
- // workaround for https://github.com/vector-im/element-ios/issues/2353
- sticker.info.thumbnail_url = sticker.url;
- sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
- sticker.info.thumbnail_info.size = sticker.info.size;
- sticker.info.thumbnail_info.h = sticker.info.h;
- sticker.info.thumbnail_info.w = sticker.info.w;
-
- if (!room->reply().isEmpty()) {
- sticker.relations.relations.push_back(
- {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
- }
- if (!room->edit().isEmpty()) {
- sticker.relations.relations.push_back(
- {mtx::common::RelationType::Replace, room->edit().toStdString()});
- }
-
- room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
+ if (!model || row < 0)
+ return;
+
+ auto img = model->imageAt(row);
+
+ mtx::events::msg::StickerImage sticker{};
+ sticker.info = img.info.value_or(mtx::common::ImageInfo{});
+ sticker.url = img.url;
+ sticker.body = img.body;
+
+ // workaround for https://github.com/vector-im/element-ios/issues/2353
+ sticker.info.thumbnail_url = sticker.url;
+ sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
+ sticker.info.thumbnail_info.size = sticker.info.size;
+ sticker.info.thumbnail_info.h = sticker.info.h;
+ sticker.info.thumbnail_info.w = sticker.info.w;
+
+ if (!room->reply().isEmpty()) {
+ sticker.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ sticker.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
}
void
InputBar::command(QString command, QString args)
{
- if (command == "me") {
- emote(args, false);
- } else if (command == "react") {
- auto eventId = room->reply();
- if (!eventId.isEmpty())
- reaction(eventId, args.trimmed());
- } else if (command == "join") {
- ChatPage::instance()->joinRoom(args);
- } else if (command == "part" || command == "leave") {
- MainWindow::instance()->openLeaveRoomDialog(room->roomId());
- } else if (command == "invite") {
- ChatPage::instance()->inviteUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
- } else if (command == "kick") {
- ChatPage::instance()->kickUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
- } else if (command == "ban") {
- ChatPage::instance()->banUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
- } else if (command == "unban") {
- ChatPage::instance()->unbanUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
- } else if (command == "roomnick") {
- mtx::events::state::Member member;
- member.display_name = args.toStdString();
- member.avatar_url =
- cache::avatarUrl(room->roomId(),
- QString::fromStdString(http::client()->user_id().to_string()))
- .toStdString();
- member.membership = mtx::events::state::Membership::Join;
-
- http::client()->send_state_event(
- room->roomId().toStdString(),
- http::client()->user_id().to_string(),
- member,
- [](mtx::responses::EventId, mtx::http::RequestErr err) {
- if (err)
- nhlog::net()->error("Failed to set room displayname: {}",
- err->matrix_error.error);
- });
- } else if (command == "shrug") {
- message("¯\\_(ツ)_/¯" + (args.isEmpty() ? "" : " " + args));
- } else if (command == "fliptable") {
- message("(╯°□°)╯︵ ┻━┻");
- } else if (command == "unfliptable") {
- message(" ┯━┯╭( º _ º╭)");
- } else if (command == "sovietflip") {
- message("ノ┬─┬ノ ︵ ( \\o°o)\\");
- } else if (command == "clear-timeline") {
- room->clearTimeline();
- } else if (command == "rotate-megolm-session") {
- cache::dropOutboundMegolmSession(room->roomId().toStdString());
- } else if (command == "md") {
- message(args, MarkdownOverride::ON);
- } else if (command == "plain") {
- message(args, MarkdownOverride::OFF);
- } else if (command == "rainbow") {
- message(args, MarkdownOverride::ON, true);
- } else if (command == "rainbowme") {
- emote(args, true);
- } else if (command == "notice") {
- notice(args, false);
- } else if (command == "rainbownotice") {
- notice(args, true);
- } else if (command == "goto") {
- // Goto has three different modes:
- // 1 - Going directly to a given event ID
- if (args[0] == '$') {
- room->showEvent(args);
- return;
- }
- // 2 - Going directly to a given message index
- if (args[0] >= '0' && args[0] <= '9') {
- room->showEvent(args);
- return;
- }
- // 3 - Matrix URI handler, as if you clicked the URI
- if (ChatPage::instance()->handleMatrixUri(args)) {
- return;
- }
- nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
+ if (command == "me") {
+ emote(args, false);
+ } else if (command == "react") {
+ auto eventId = room->reply();
+ if (!eventId.isEmpty())
+ reaction(eventId, args.trimmed());
+ } else if (command == "join") {
+ ChatPage::instance()->joinRoom(args);
+ } else if (command == "part" || command == "leave") {
+ MainWindow::instance()->openLeaveRoomDialog(room->roomId());
+ } else if (command == "invite") {
+ ChatPage::instance()->inviteUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
+ } else if (command == "kick") {
+ ChatPage::instance()->kickUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
+ } else if (command == "ban") {
+ ChatPage::instance()->banUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
+ } else if (command == "unban") {
+ ChatPage::instance()->unbanUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
+ } else if (command == "roomnick") {
+ mtx::events::state::Member member;
+ member.display_name = args.toStdString();
+ member.avatar_url =
+ cache::avatarUrl(room->roomId(),
+ QString::fromStdString(http::client()->user_id().to_string()))
+ .toStdString();
+ member.membership = mtx::events::state::Membership::Join;
+
+ http::client()->send_state_event(room->roomId().toStdString(),
+ http::client()->user_id().to_string(),
+ member,
+ [](mtx::responses::EventId, mtx::http::RequestErr err) {
+ if (err)
+ nhlog::net()->error(
+ "Failed to set room displayname: {}",
+ err->matrix_error.error);
+ });
+ } else if (command == "shrug") {
+ message("¯\\_(ツ)_/¯" + (args.isEmpty() ? "" : " " + args));
+ } else if (command == "fliptable") {
+ message("(╯°□°)╯︵ ┻━┻");
+ } else if (command == "unfliptable") {
+ message(" ┯━┯╭( º _ º╭)");
+ } else if (command == "sovietflip") {
+ message("ノ┬─┬ノ ︵ ( \\o°o)\\");
+ } else if (command == "clear-timeline") {
+ room->clearTimeline();
+ } else if (command == "rotate-megolm-session") {
+ cache::dropOutboundMegolmSession(room->roomId().toStdString());
+ } else if (command == "md") {
+ message(args, MarkdownOverride::ON);
+ } else if (command == "plain") {
+ message(args, MarkdownOverride::OFF);
+ } else if (command == "rainbow") {
+ message(args, MarkdownOverride::ON, true);
+ } else if (command == "rainbowme") {
+ emote(args, true);
+ } else if (command == "notice") {
+ notice(args, false);
+ } else if (command == "rainbownotice") {
+ notice(args, true);
+ } else if (command == "goto") {
+ // Goto has three different modes:
+ // 1 - Going directly to a given event ID
+ if (args[0] == '$') {
+ room->showEvent(args);
+ return;
+ }
+ // 2 - Going directly to a given message index
+ if (args[0] >= '0' && args[0] <= '9') {
+ room->showEvent(args);
+ return;
+ }
+ // 3 - Matrix URI handler, as if you clicked the URI
+ if (ChatPage::instance()->handleMatrixUri(args)) {
+ return;
}
+ nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
+ }
}
void
InputBar::showPreview(const QMimeData &source, QString path, const QStringList &formats)
{
- dialogs::PreviewUploadOverlay *previewDialog_ =
- new dialogs::PreviewUploadOverlay(ChatPage::instance());
- previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
-
- if (source.hasImage())
- previewDialog_->setPreview(qvariant_cast<QImage>(source.imageData()),
- formats.front());
- else if (!path.isEmpty())
- previewDialog_->setPreview(path);
- else if (!formats.isEmpty()) {
- auto mime = formats.first();
- previewDialog_->setPreview(source.data(mime), mime);
- } else {
- setUploading(false);
- previewDialog_->deleteLater();
- return;
- }
-
- connect(previewDialog_, &dialogs::PreviewUploadOverlay::aborted, this, [this]() {
- setUploading(false);
- });
-
- connect(
- previewDialog_,
- &dialogs::PreviewUploadOverlay::confirmUpload,
- this,
- [this](const QByteArray data, const QString &mime, const QString &fn) {
- setUploading(true);
-
- setText("");
-
- auto payload = std::string(data.data(), data.size());
- std::optional<mtx::crypto::EncryptedFile> encryptedFile;
- if (cache::isRoomEncrypted(room->roomId().toStdString())) {
- mtx::crypto::BinaryBuf buf;
- std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
- payload = mtx::crypto::to_string(buf);
+ dialogs::PreviewUploadOverlay *previewDialog_ =
+ new dialogs::PreviewUploadOverlay(ChatPage::instance());
+ previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
+
+ if (source.hasImage())
+ previewDialog_->setPreview(qvariant_cast<QImage>(source.imageData()), formats.front());
+ else if (!path.isEmpty())
+ previewDialog_->setPreview(path);
+ else if (!formats.isEmpty()) {
+ auto mime = formats.first();
+ previewDialog_->setPreview(source.data(mime), mime);
+ } else {
+ setUploading(false);
+ previewDialog_->deleteLater();
+ return;
+ }
+
+ connect(previewDialog_, &dialogs::PreviewUploadOverlay::aborted, this, [this]() {
+ setUploading(false);
+ });
+
+ connect(
+ previewDialog_,
+ &dialogs::PreviewUploadOverlay::confirmUpload,
+ this,
+ [this](const QByteArray data, const QString &mime, const QString &fn) {
+ setUploading(true);
+
+ setText("");
+
+ auto payload = std::string(data.data(), data.size());
+ std::optional<mtx::crypto::EncryptedFile> encryptedFile;
+ if (cache::isRoomEncrypted(room->roomId().toStdString())) {
+ mtx::crypto::BinaryBuf buf;
+ std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
+ payload = mtx::crypto::to_string(buf);
+ }
+
+ QSize dimensions;
+ QString blurhash;
+ auto mimeClass = mime.split("/")[0];
+ nhlog::ui()->debug("Mime: {}", mime.toStdString());
+ if (mimeClass == "image") {
+ QImage img = utils::readImage(data);
+
+ dimensions = img.size();
+ if (img.height() > 200 && img.width() > 360)
+ img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
+ std::vector<unsigned char> data_;
+ for (int y = 0; y < img.height(); y++) {
+ for (int x = 0; x < img.width(); x++) {
+ auto p = img.pixel(x, y);
+ data_.push_back(static_cast<unsigned char>(qRed(p)));
+ data_.push_back(static_cast<unsigned char>(qGreen(p)));
+ data_.push_back(static_cast<unsigned char>(qBlue(p)));
}
+ }
+ blurhash = QString::fromStdString(
+ blurhash::encode(data_.data(), img.width(), img.height(), 4, 3));
+ }
+
+ http::client()->upload(
+ payload,
+ encryptedFile ? "application/octet-stream" : mime.toStdString(),
+ QFileInfo(fn).fileName().toStdString(),
+ [this,
+ filename = fn,
+ encryptedFile = std::move(encryptedFile),
+ mimeClass,
+ mime,
+ size = payload.size(),
+ dimensions,
+ blurhash](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable {
+ if (err) {
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to upload media. Please try again."));
+ nhlog::net()->warn("failed to upload media: {} {} ({})",
+ err->matrix_error.error,
+ to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ setUploading(false);
+ return;
+ }
- QSize dimensions;
- QString blurhash;
- auto mimeClass = mime.split("/")[0];
- nhlog::ui()->debug("Mime: {}", mime.toStdString());
- if (mimeClass == "image") {
- QImage img = utils::readImage(data);
-
- dimensions = img.size();
- if (img.height() > 200 && img.width() > 360)
- img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
- std::vector<unsigned char> data_;
- for (int y = 0; y < img.height(); y++) {
- for (int x = 0; x < img.width(); x++) {
- auto p = img.pixel(x, y);
- data_.push_back(static_cast<unsigned char>(qRed(p)));
- data_.push_back(static_cast<unsigned char>(qGreen(p)));
- data_.push_back(static_cast<unsigned char>(qBlue(p)));
- }
- }
- blurhash = QString::fromStdString(
- blurhash::encode(data_.data(), img.width(), img.height(), 4, 3));
- }
+ auto url = QString::fromStdString(res.content_uri);
+ if (encryptedFile)
+ encryptedFile->url = res.content_uri;
+
+ if (mimeClass == "image")
+ image(filename, encryptedFile, url, mime, size, dimensions, blurhash);
+ else if (mimeClass == "audio")
+ audio(filename, encryptedFile, url, mime, size);
+ else if (mimeClass == "video")
+ video(filename, encryptedFile, url, mime, size);
+ else
+ file(filename, encryptedFile, url, mime, size);
- http::client()->upload(
- payload,
- encryptedFile ? "application/octet-stream" : mime.toStdString(),
- QFileInfo(fn).fileName().toStdString(),
- [this,
- filename = fn,
- encryptedFile = std::move(encryptedFile),
- mimeClass,
- mime,
- size = payload.size(),
- dimensions,
- blurhash](const mtx::responses::ContentURI &res,
- mtx::http::RequestErr err) mutable {
- if (err) {
- emit ChatPage::instance()->showNotification(
- tr("Failed to upload media. Please try again."));
- nhlog::net()->warn("failed to upload media: {} {} ({})",
- err->matrix_error.error,
- to_string(err->matrix_error.errcode),
- static_cast<int>(err->status_code));
- setUploading(false);
- return;
- }
-
- auto url = QString::fromStdString(res.content_uri);
- if (encryptedFile)
- encryptedFile->url = res.content_uri;
-
- if (mimeClass == "image")
- image(filename,
- encryptedFile,
- url,
- mime,
- size,
- dimensions,
- blurhash);
- else if (mimeClass == "audio")
- audio(filename, encryptedFile, url, mime, size);
- else if (mimeClass == "video")
- video(filename, encryptedFile, url, mime, size);
- else
- file(filename, encryptedFile, url, mime, size);
-
- setUploading(false);
- });
- });
+ setUploading(false);
+ });
+ });
}
void
InputBar::startTyping()
{
- if (!typingRefresh_.isActive()) {
- typingRefresh_.start();
-
- if (ChatPage::instance()->userSettings()->typingNotifications()) {
- http::client()->start_typing(
- room->roomId().toStdString(), 10'000, [](mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn(
- "failed to send typing notification: {}",
- err->matrix_error.error);
- }
- });
- }
+ if (!typingRefresh_.isActive()) {
+ typingRefresh_.start();
+
+ if (ChatPage::instance()->userSettings()->typingNotifications()) {
+ http::client()->start_typing(
+ room->roomId().toStdString(), 10'000, [](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to send typing notification: {}",
+ err->matrix_error.error);
+ }
+ });
}
- typingTimeout_.start();
+ }
+ typingTimeout_.start();
}
void
InputBar::stopTyping()
{
- typingRefresh_.stop();
- typingTimeout_.stop();
+ typingRefresh_.stop();
+ typingTimeout_.stop();
- if (!ChatPage::instance()->userSettings()->typingNotifications())
- return;
+ if (!ChatPage::instance()->userSettings()->typingNotifications())
+ return;
- http::client()->stop_typing(room->roomId().toStdString(), [](mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to stop typing notifications: {}",
- err->matrix_error.error);
- }
- });
+ http::client()->stop_typing(room->roomId().toStdString(), [](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to stop typing notifications: {}", err->matrix_error.error);
+ }
+ });
}
void
InputBar::reaction(const QString &reactedEvent, const QString &reactionKey)
{
- auto reactions = room->reactions(reactedEvent.toStdString());
-
- QString selfReactedEvent;
- for (const auto &reaction : reactions) {
- if (reactionKey == reaction.key_) {
- selfReactedEvent = reaction.selfReactedEvent_;
- break;
- }
- }
+ auto reactions = room->reactions(reactedEvent.toStdString());
- if (selfReactedEvent.startsWith("m"))
- return;
-
- // If selfReactedEvent is empty, that means we haven't previously reacted
- if (selfReactedEvent.isEmpty()) {
- mtx::events::msg::Reaction reaction;
- mtx::common::Relation rel;
- rel.rel_type = mtx::common::RelationType::Annotation;
- rel.event_id = reactedEvent.toStdString();
- rel.key = reactionKey.toStdString();
- reaction.relations.relations.push_back(rel);
-
- room->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
- // Otherwise, we have previously reacted and the reaction should be redacted
- } else {
- room->redactEvent(selfReactedEvent);
+ QString selfReactedEvent;
+ for (const auto &reaction : reactions) {
+ if (reactionKey == reaction.key_) {
+ selfReactedEvent = reaction.selfReactedEvent_;
+ break;
}
+ }
+
+ if (selfReactedEvent.startsWith("m"))
+ return;
+
+ // If selfReactedEvent is empty, that means we haven't previously reacted
+ if (selfReactedEvent.isEmpty()) {
+ mtx::events::msg::Reaction reaction;
+ mtx::common::Relation rel;
+ rel.rel_type = mtx::common::RelationType::Annotation;
+ rel.event_id = reactedEvent.toStdString();
+ rel.key = reactionKey.toStdString();
+ reaction.relations.relations.push_back(rel);
+
+ room->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
+ // Otherwise, we have previously reacted and the reaction should be redacted
+ } else {
+ room->redactEvent(selfReactedEvent);
+ }
}
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index cdc66a06..4a0f4401 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -19,105 +19,105 @@ class QStringList;
enum class MarkdownOverride
{
- NOT_SPECIFIED, // no override set
- ON,
- OFF,
+ NOT_SPECIFIED, // no override set
+ ON,
+ OFF,
};
class InputBar : public QObject
{
- Q_OBJECT
- Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
- Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
- Q_PROPERTY(QString text READ text NOTIFY textChanged)
+ Q_OBJECT
+ Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
+ Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
+ Q_PROPERTY(QString text READ text NOTIFY textChanged)
public:
- InputBar(TimelineModel *parent)
- : QObject()
- , room(parent)
- {
- typingRefresh_.setInterval(10'000);
- typingRefresh_.setSingleShot(true);
- typingTimeout_.setInterval(5'000);
- typingTimeout_.setSingleShot(true);
- connect(&typingRefresh_, &QTimer::timeout, this, &InputBar::startTyping);
- connect(&typingTimeout_, &QTimer::timeout, this, &InputBar::stopTyping);
- }
+ InputBar(TimelineModel *parent)
+ : QObject()
+ , room(parent)
+ {
+ typingRefresh_.setInterval(10'000);
+ typingRefresh_.setSingleShot(true);
+ typingTimeout_.setInterval(5'000);
+ typingTimeout_.setSingleShot(true);
+ connect(&typingRefresh_, &QTimer::timeout, this, &InputBar::startTyping);
+ connect(&typingTimeout_, &QTimer::timeout, this, &InputBar::stopTyping);
+ }
public slots:
- QString text() const;
- QString previousText();
- QString nextText();
- void setText(QString newText);
-
- bool containsAtRoom() const { return containsAtRoom_; }
-
- void send();
- void paste(bool fromMouse);
- void insertMimeData(const QMimeData *data);
- void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text);
- void openFileSelection();
- bool uploading() const { return uploading_; }
- void message(QString body,
- MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
- bool rainbowify = false);
- void reaction(const QString &reactedEvent, const QString &reactionKey);
- void sticker(CombinedImagePackModel *model, int row);
+ QString text() const;
+ QString previousText();
+ QString nextText();
+ void setText(QString newText);
+
+ bool containsAtRoom() const { return containsAtRoom_; }
+
+ void send();
+ void paste(bool fromMouse);
+ void insertMimeData(const QMimeData *data);
+ void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text);
+ void openFileSelection();
+ bool uploading() const { return uploading_; }
+ void message(QString body,
+ MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
+ bool rainbowify = false);
+ void reaction(const QString &reactedEvent, const QString &reactionKey);
+ void sticker(CombinedImagePackModel *model, int row);
private slots:
- void startTyping();
- void stopTyping();
+ void startTyping();
+ void stopTyping();
signals:
- void insertText(QString text);
- void textChanged(QString newText);
- void uploadingChanged(bool value);
- void containsAtRoomChanged();
+ void insertText(QString text);
+ void textChanged(QString newText);
+ void uploadingChanged(bool value);
+ void containsAtRoomChanged();
private:
- void emote(QString body, bool rainbowify);
- void notice(QString body, bool rainbowify);
- void command(QString name, QString args);
- void image(const QString &filename,
- const std::optional<mtx::crypto::EncryptedFile> &file,
- const QString &url,
- const QString &mime,
- uint64_t dsize,
- const QSize &dimensions,
- const QString &blurhash);
- void file(const QString &filename,
- const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
- const QString &url,
- const QString &mime,
- uint64_t dsize);
- void audio(const QString &filename,
- const std::optional<mtx::crypto::EncryptedFile> &file,
- const QString &url,
- const QString &mime,
- uint64_t dsize);
- void video(const QString &filename,
- const std::optional<mtx::crypto::EncryptedFile> &file,
- const QString &url,
- const QString &mime,
- uint64_t dsize);
-
- void showPreview(const QMimeData &source, QString path, const QStringList &formats);
- void setUploading(bool value)
- {
- if (value != uploading_) {
- uploading_ = value;
- emit uploadingChanged(value);
- }
+ void emote(QString body, bool rainbowify);
+ void notice(QString body, bool rainbowify);
+ void command(QString name, QString args);
+ void image(const QString &filename,
+ const std::optional<mtx::crypto::EncryptedFile> &file,
+ const QString &url,
+ const QString &mime,
+ uint64_t dsize,
+ const QSize &dimensions,
+ const QString &blurhash);
+ void file(const QString &filename,
+ const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
+ const QString &url,
+ const QString &mime,
+ uint64_t dsize);
+ void audio(const QString &filename,
+ const std::optional<mtx::crypto::EncryptedFile> &file,
+ const QString &url,
+ const QString &mime,
+ uint64_t dsize);
+ void video(const QString &filename,
+ const std::optional<mtx::crypto::EncryptedFile> &file,
+ const QString &url,
+ const QString &mime,
+ uint64_t dsize);
+
+ void showPreview(const QMimeData &source, QString path, const QStringList &formats);
+ void setUploading(bool value)
+ {
+ if (value != uploading_) {
+ uploading_ = value;
+ emit uploadingChanged(value);
}
-
- void updateAtRoom(const QString &t);
-
- QTimer typingRefresh_;
- QTimer typingTimeout_;
- TimelineModel *room;
- std::deque<QString> history_;
- std::size_t history_index_ = 0;
- int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
- bool uploading_ = false;
- bool containsAtRoom_ = false;
+ }
+
+ void updateAtRoom(const QString &t);
+
+ QTimer typingRefresh_;
+ QTimer typingTimeout_;
+ TimelineModel *room;
+ std::deque<QString> history_;
+ std::size_t history_index_ = 0;
+ int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
+ bool uploading_ = false;
+ bool containsAtRoom_ = false;
};
diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp
index 5dafc325..4e45f2e2 100644
--- a/src/timeline/Permissions.cpp
+++ b/src/timeline/Permissions.cpp
@@ -12,59 +12,59 @@ Permissions::Permissions(QString roomId, QObject *parent)
: QObject(parent)
, roomId_(roomId)
{
- invalidate();
+ invalidate();
}
void
Permissions::invalidate()
{
- pl = cache::client()
- ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
- .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
- .content;
+ pl = cache::client()
+ ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
+ .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
+ .content;
}
bool
Permissions::canInvite()
{
- return pl.user_level(http::client()->user_id().to_string()) >= pl.invite;
+ return pl.user_level(http::client()->user_id().to_string()) >= pl.invite;
}
bool
Permissions::canBan()
{
- return pl.user_level(http::client()->user_id().to_string()) >= pl.ban;
+ return pl.user_level(http::client()->user_id().to_string()) >= pl.ban;
}
bool
Permissions::canKick()
{
- return pl.user_level(http::client()->user_id().to_string()) >= pl.kick;
+ return pl.user_level(http::client()->user_id().to_string()) >= pl.kick;
}
bool
Permissions::canRedact()
{
- return pl.user_level(http::client()->user_id().to_string()) >= pl.redact;
+ return pl.user_level(http::client()->user_id().to_string()) >= pl.redact;
}
bool
Permissions::canChange(int eventType)
{
- return pl.user_level(http::client()->user_id().to_string()) >=
- pl.state_level(to_string(qml_mtx_events::fromRoomEventType(
- static_cast<qml_mtx_events::EventType>(eventType))));
+ return pl.user_level(http::client()->user_id().to_string()) >=
+ pl.state_level(to_string(
+ qml_mtx_events::fromRoomEventType(static_cast<qml_mtx_events::EventType>(eventType))));
}
bool
Permissions::canSend(int eventType)
{
- return pl.user_level(http::client()->user_id().to_string()) >=
- pl.event_level(to_string(qml_mtx_events::fromRoomEventType(
- static_cast<qml_mtx_events::EventType>(eventType))));
+ return pl.user_level(http::client()->user_id().to_string()) >=
+ pl.event_level(to_string(
+ qml_mtx_events::fromRoomEventType(static_cast<qml_mtx_events::EventType>(eventType))));
}
bool
Permissions::canPingRoom()
{
- return pl.user_level(http::client()->user_id().to_string()) >=
- pl.notification_level(mtx::events::state::notification_keys::room);
+ return pl.user_level(http::client()->user_id().to_string()) >=
+ pl.notification_level(mtx::events::state::notification_keys::room);
}
diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h
index 349520d5..b80a66aa 100644
--- a/src/timeline/Permissions.h
+++ b/src/timeline/Permissions.h
@@ -12,24 +12,24 @@ class TimelineModel;
class Permissions : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
- Permissions(QString roomId, QObject *parent = nullptr);
+ Permissions(QString roomId, QObject *parent = nullptr);
- Q_INVOKABLE bool canInvite();
- Q_INVOKABLE bool canBan();
- Q_INVOKABLE bool canKick();
+ Q_INVOKABLE bool canInvite();
+ Q_INVOKABLE bool canBan();
+ Q_INVOKABLE bool canKick();
- Q_INVOKABLE bool canRedact();
- Q_INVOKABLE bool canChange(int eventType);
- Q_INVOKABLE bool canSend(int eventType);
+ Q_INVOKABLE bool canRedact();
+ Q_INVOKABLE bool canChange(int eventType);
+ Q_INVOKABLE bool canSend(int eventType);
- Q_INVOKABLE bool canPingRoom();
+ Q_INVOKABLE bool canPingRoom();
- void invalidate();
+ void invalidate();
private:
- QString roomId_;
- mtx::events::state::PowerLevels pl;
+ QString roomId_;
+ mtx::events::state::PowerLevels pl;
};
diff --git a/src/timeline/Reaction.h b/src/timeline/Reaction.h
index 788e9ced..fcdd61a4 100644
--- a/src/timeline/Reaction.h
+++ b/src/timeline/Reaction.h
@@ -9,20 +9,20 @@
struct Reaction
{
- Q_GADGET
- Q_PROPERTY(QString key READ key)
- Q_PROPERTY(QString users READ users)
- Q_PROPERTY(QString selfReactedEvent READ selfReactedEvent)
- Q_PROPERTY(int count READ count)
+ Q_GADGET
+ Q_PROPERTY(QString key READ key)
+ Q_PROPERTY(QString users READ users)
+ Q_PROPERTY(QString selfReactedEvent READ selfReactedEvent)
+ Q_PROPERTY(int count READ count)
public:
- QString key() const { return key_.toHtmlEscaped(); }
- QString users() const { return users_.toHtmlEscaped(); }
- QString selfReactedEvent() const { return selfReactedEvent_; }
- int count() const { return count_; }
+ QString key() const { return key_.toHtmlEscaped(); }
+ QString users() const { return users_.toHtmlEscaped(); }
+ QString selfReactedEvent() const { return selfReactedEvent_; }
+ int count() const { return count_; }
- QString key_;
- QString users_;
- QString selfReactedEvent_;
- int count_;
+ QString key_;
+ QString users_;
+ QString selfReactedEvent_;
+ int count_;
};
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 2d1dd49d..2d60dcb3 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -17,978 +17,947 @@ RoomlistModel::RoomlistModel(TimelineViewManager *parent)
: QAbstractListModel(parent)
, manager(parent)
{
- [[maybe_unused]] static auto id = qRegisterMetaType<RoomPreview>();
-
- connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() {
- auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar();
- QHash<QString, QSharedPointer<TimelineModel>>::iterator i;
- for (i = models.begin(); i != models.end(); ++i) {
- auto ptr = i.value();
-
- if (!ptr.isNull()) {
- ptr->setDecryptDescription(decrypt);
- ptr->updateLastMessage();
- }
- }
- });
-
- connect(this,
- &RoomlistModel::totalUnreadMessageCountUpdated,
- ChatPage::instance(),
- &ChatPage::unreadMessages);
-
- connect(
- this,
- &RoomlistModel::fetchedPreview,
- this,
- [this](QString roomid, RoomInfo info) {
- if (this->previewedRooms.contains(roomid)) {
- this->previewedRooms.insert(roomid, std::move(info));
- auto idx = this->roomidToIndex(roomid);
- emit dataChanged(index(idx),
- index(idx),
- {
- Roles::RoomName,
- Roles::AvatarUrl,
- Roles::IsSpace,
- Roles::IsPreviewFetched,
- Qt::DisplayRole,
- });
- }
- },
- Qt::QueuedConnection);
+ [[maybe_unused]] static auto id = qRegisterMetaType<RoomPreview>();
+
+ connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() {
+ auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar();
+ QHash<QString, QSharedPointer<TimelineModel>>::iterator i;
+ for (i = models.begin(); i != models.end(); ++i) {
+ auto ptr = i.value();
+
+ if (!ptr.isNull()) {
+ ptr->setDecryptDescription(decrypt);
+ ptr->updateLastMessage();
+ }
+ }
+ });
+
+ connect(this,
+ &RoomlistModel::totalUnreadMessageCountUpdated,
+ ChatPage::instance(),
+ &ChatPage::unreadMessages);
+
+ connect(
+ this,
+ &RoomlistModel::fetchedPreview,
+ this,
+ [this](QString roomid, RoomInfo info) {
+ if (this->previewedRooms.contains(roomid)) {
+ this->previewedRooms.insert(roomid, std::move(info));
+ auto idx = this->roomidToIndex(roomid);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::RoomName,
+ Roles::AvatarUrl,
+ Roles::IsSpace,
+ Roles::IsPreviewFetched,
+ Qt::DisplayRole,
+ });
+ }
+ },
+ Qt::QueuedConnection);
}
QHash<int, QByteArray>
RoomlistModel::roleNames() const
{
- return {
- {AvatarUrl, "avatarUrl"},
- {RoomName, "roomName"},
- {RoomId, "roomId"},
- {LastMessage, "lastMessage"},
- {Time, "time"},
- {Timestamp, "timestamp"},
- {HasUnreadMessages, "hasUnreadMessages"},
- {HasLoudNotification, "hasLoudNotification"},
- {NotificationCount, "notificationCount"},
- {IsInvite, "isInvite"},
- {IsSpace, "isSpace"},
- {Tags, "tags"},
- {ParentSpaces, "parentSpaces"},
- {IsDirect, "isDirect"},
- {DirectChatOtherUserId, "directChatOtherUserId"},
- };
+ return {
+ {AvatarUrl, "avatarUrl"},
+ {RoomName, "roomName"},
+ {RoomId, "roomId"},
+ {LastMessage, "lastMessage"},
+ {Time, "time"},
+ {Timestamp, "timestamp"},
+ {HasUnreadMessages, "hasUnreadMessages"},
+ {HasLoudNotification, "hasLoudNotification"},
+ {NotificationCount, "notificationCount"},
+ {IsInvite, "isInvite"},
+ {IsSpace, "isSpace"},
+ {Tags, "tags"},
+ {ParentSpaces, "parentSpaces"},
+ {IsDirect, "isDirect"},
+ {DirectChatOtherUserId, "directChatOtherUserId"},
+ };
}
QVariant
RoomlistModel::data(const QModelIndex &index, int role) const
{
- if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) {
- auto roomid = roomids.at(index.row());
-
- if (role == Roles::ParentSpaces) {
- auto parents = cache::client()->getParentRoomIds(roomid.toStdString());
- QStringList list;
- for (const auto &t : parents)
- list.push_back(QString::fromStdString(t));
- return list;
- } else if (role == Roles::RoomId) {
- return roomid;
- }
+ if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) {
+ auto roomid = roomids.at(index.row());
+
+ if (role == Roles::ParentSpaces) {
+ auto parents = cache::client()->getParentRoomIds(roomid.toStdString());
+ QStringList list;
+ for (const auto &t : parents)
+ list.push_back(QString::fromStdString(t));
+ return list;
+ } else if (role == Roles::RoomId) {
+ return roomid;
+ }
- if (models.contains(roomid)) {
- auto room = models.value(roomid);
- switch (role) {
- case Roles::AvatarUrl:
- return room->roomAvatarUrl();
- case Roles::RoomName:
- return room->plainRoomName();
- case Roles::LastMessage:
- return room->lastMessage().body;
- case Roles::Time:
- return room->lastMessage().descriptiveTime;
- case Roles::Timestamp:
- return QVariant(
- static_cast<quint64>(room->lastMessage().timestamp));
- case Roles::HasUnreadMessages:
- return this->roomReadStatus.count(roomid) &&
- this->roomReadStatus.at(roomid);
- case Roles::HasLoudNotification:
- return room->hasMentions();
- case Roles::NotificationCount:
- return room->notificationCount();
- case Roles::IsInvite:
- return false;
- case Roles::IsSpace:
- return room->isSpace();
- case Roles::IsPreview:
- return false;
- case Roles::Tags: {
- auto info = cache::singleRoomInfo(roomid.toStdString());
- QStringList list;
- for (const auto &t : info.tags)
- list.push_back(QString::fromStdString(t));
- return list;
- }
- case Roles::IsDirect:
- return room->isDirect();
- case Roles::DirectChatOtherUserId:
- return room->directChatOtherUserId();
- default:
- return {};
- }
- } else if (invites.contains(roomid)) {
- auto room = invites.value(roomid);
- switch (role) {
- case Roles::AvatarUrl:
- return QString::fromStdString(room.avatar_url);
- case Roles::RoomName:
- return QString::fromStdString(room.name);
- case Roles::LastMessage:
- return tr("Pending invite.");
- case Roles::Time:
- return QString();
- case Roles::Timestamp:
- return QVariant(static_cast<quint64>(0));
- case Roles::HasUnreadMessages:
- case Roles::HasLoudNotification:
- return false;
- case Roles::NotificationCount:
- return 0;
- case Roles::IsInvite:
- return true;
- case Roles::IsSpace:
- return false;
- case Roles::IsPreview:
- return false;
- case Roles::Tags:
- return QStringList();
- case Roles::IsDirect:
- // The list of users from the room doesn't contain the invited
- // users, so we won't factor the invite into the count
- return room.member_count == 1;
- case Roles::DirectChatOtherUserId:
- return cache::getMembersFromInvite(roomid.toStdString(), 0, 1)
- .front()
- .user_id;
- default:
- return {};
- }
- } else if (previewedRooms.contains(roomid) &&
- previewedRooms.value(roomid).has_value()) {
- auto room = previewedRooms.value(roomid).value();
- switch (role) {
- case Roles::AvatarUrl:
- return QString::fromStdString(room.avatar_url);
- case Roles::RoomName:
- return QString::fromStdString(room.name);
- case Roles::LastMessage:
- return tr("Previewing this room");
- case Roles::Time:
- return QString();
- case Roles::Timestamp:
- return QVariant(static_cast<quint64>(0));
- case Roles::HasUnreadMessages:
- case Roles::HasLoudNotification:
- return false;
- case Roles::NotificationCount:
- return 0;
- case Roles::IsInvite:
- return false;
- case Roles::IsSpace:
- return room.is_space;
- case Roles::IsPreview:
- return true;
- case Roles::IsPreviewFetched:
- return true;
- case Roles::Tags:
- return QStringList();
- case Roles::IsDirect:
- return false;
- case Roles::DirectChatOtherUserId:
- return QString{}; // should never be reached
- default:
- return {};
- }
- } else {
- if (role == Roles::IsPreview)
- return true;
- else if (role == Roles::IsPreviewFetched)
- return false;
-
- fetchPreview(roomid);
- switch (role) {
- case Roles::AvatarUrl:
- return QString();
- case Roles::RoomName:
- return tr("No preview available");
- case Roles::LastMessage:
- return QString();
- case Roles::Time:
- return QString();
- case Roles::Timestamp:
- return QVariant(static_cast<quint64>(0));
- case Roles::HasUnreadMessages:
- case Roles::HasLoudNotification:
- return false;
- case Roles::NotificationCount:
- return 0;
- case Roles::IsInvite:
- return false;
- case Roles::IsSpace:
- return false;
- case Roles::Tags:
- return QStringList();
- default:
- return {};
- }
- }
+ if (models.contains(roomid)) {
+ auto room = models.value(roomid);
+ switch (role) {
+ case Roles::AvatarUrl:
+ return room->roomAvatarUrl();
+ case Roles::RoomName:
+ return room->plainRoomName();
+ case Roles::LastMessage:
+ return room->lastMessage().body;
+ case Roles::Time:
+ return room->lastMessage().descriptiveTime;
+ case Roles::Timestamp:
+ return QVariant(static_cast<quint64>(room->lastMessage().timestamp));
+ case Roles::HasUnreadMessages:
+ return this->roomReadStatus.count(roomid) && this->roomReadStatus.at(roomid);
+ case Roles::HasLoudNotification:
+ return room->hasMentions();
+ case Roles::NotificationCount:
+ return room->notificationCount();
+ case Roles::IsInvite:
+ return false;
+ case Roles::IsSpace:
+ return room->isSpace();
+ case Roles::IsPreview:
+ return false;
+ case Roles::Tags: {
+ auto info = cache::singleRoomInfo(roomid.toStdString());
+ QStringList list;
+ for (const auto &t : info.tags)
+ list.push_back(QString::fromStdString(t));
+ return list;
+ }
+ case Roles::IsDirect:
+ return room->isDirect();
+ case Roles::DirectChatOtherUserId:
+ return room->directChatOtherUserId();
+ default:
+ return {};
+ }
+ } else if (invites.contains(roomid)) {
+ auto room = invites.value(roomid);
+ switch (role) {
+ case Roles::AvatarUrl:
+ return QString::fromStdString(room.avatar_url);
+ case Roles::RoomName:
+ return QString::fromStdString(room.name);
+ case Roles::LastMessage:
+ return tr("Pending invite.");
+ case Roles::Time:
+ return QString();
+ case Roles::Timestamp:
+ return QVariant(static_cast<quint64>(0));
+ case Roles::HasUnreadMessages:
+ case Roles::HasLoudNotification:
+ return false;
+ case Roles::NotificationCount:
+ return 0;
+ case Roles::IsInvite:
+ return true;
+ case Roles::IsSpace:
+ return false;
+ case Roles::IsPreview:
+ return false;
+ case Roles::Tags:
+ return QStringList();
+ case Roles::IsDirect:
+ // The list of users from the room doesn't contain the invited
+ // users, so we won't factor the invite into the count
+ return room.member_count == 1;
+ case Roles::DirectChatOtherUserId:
+ return cache::getMembersFromInvite(roomid.toStdString(), 0, 1).front().user_id;
+ default:
+ return {};
+ }
+ } else if (previewedRooms.contains(roomid) && previewedRooms.value(roomid).has_value()) {
+ auto room = previewedRooms.value(roomid).value();
+ switch (role) {
+ case Roles::AvatarUrl:
+ return QString::fromStdString(room.avatar_url);
+ case Roles::RoomName:
+ return QString::fromStdString(room.name);
+ case Roles::LastMessage:
+ return tr("Previewing this room");
+ case Roles::Time:
+ return QString();
+ case Roles::Timestamp:
+ return QVariant(static_cast<quint64>(0));
+ case Roles::HasUnreadMessages:
+ case Roles::HasLoudNotification:
+ return false;
+ case Roles::NotificationCount:
+ return 0;
+ case Roles::IsInvite:
+ return false;
+ case Roles::IsSpace:
+ return room.is_space;
+ case Roles::IsPreview:
+ return true;
+ case Roles::IsPreviewFetched:
+ return true;
+ case Roles::Tags:
+ return QStringList();
+ case Roles::IsDirect:
+ return false;
+ case Roles::DirectChatOtherUserId:
+ return QString{}; // should never be reached
+ default:
+ return {};
+ }
} else {
+ if (role == Roles::IsPreview)
+ return true;
+ else if (role == Roles::IsPreviewFetched)
+ return false;
+
+ fetchPreview(roomid);
+ switch (role) {
+ case Roles::AvatarUrl:
+ return QString();
+ case Roles::RoomName:
+ return tr("No preview available");
+ case Roles::LastMessage:
+ return QString();
+ case Roles::Time:
+ return QString();
+ case Roles::Timestamp:
+ return QVariant(static_cast<quint64>(0));
+ case Roles::HasUnreadMessages:
+ case Roles::HasLoudNotification:
+ return false;
+ case Roles::NotificationCount:
+ return 0;
+ case Roles::IsInvite:
+ return false;
+ case Roles::IsSpace:
+ return false;
+ case Roles::Tags:
+ return QStringList();
+ default:
return {};
+ }
}
+ } else {
+ return {};
+ }
}
void
RoomlistModel::updateReadStatus(const std::map<QString, bool> roomReadStatus_)
{
- std::vector<int> roomsToUpdate;
- roomsToUpdate.resize(roomReadStatus_.size());
- for (const auto &[roomid, roomUnread] : roomReadStatus_) {
- if (roomUnread != roomReadStatus[roomid]) {
- roomsToUpdate.push_back(this->roomidToIndex(roomid));
- }
-
- this->roomReadStatus[roomid] = roomUnread;
+ std::vector<int> roomsToUpdate;
+ roomsToUpdate.resize(roomReadStatus_.size());
+ for (const auto &[roomid, roomUnread] : roomReadStatus_) {
+ if (roomUnread != roomReadStatus[roomid]) {
+ roomsToUpdate.push_back(this->roomidToIndex(roomid));
}
- for (auto idx : roomsToUpdate) {
- emit dataChanged(index(idx),
- index(idx),
- {
- Roles::HasUnreadMessages,
- });
- }
+ this->roomReadStatus[roomid] = roomUnread;
+ }
+
+ for (auto idx : roomsToUpdate) {
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::HasUnreadMessages,
+ });
+ }
}
void
RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
{
- if (!models.contains(room_id)) {
- // ensure we get read status updates and are only connected once
- connect(cache::client(),
- &Cache::roomReadStatus,
- this,
- &RoomlistModel::updateReadStatus,
- Qt::UniqueConnection);
-
- QSharedPointer<TimelineModel> newRoom(new TimelineModel(manager, room_id));
- newRoom->setDecryptDescription(
- ChatPage::instance()->userSettings()->decryptSidebar());
-
- connect(newRoom.data(),
- &TimelineModel::newEncryptedImage,
- manager->imageProvider(),
- &MxcImageProvider::addEncryptionInfo);
- connect(newRoom.data(),
- &TimelineModel::forwardToRoom,
- manager,
- &TimelineViewManager::forwardMessageToRoom);
- connect(
- newRoom.data(), &TimelineModel::lastMessageChanged, this, [room_id, this]() {
- auto idx = this->roomidToIndex(room_id);
- emit dataChanged(index(idx),
- index(idx),
- {
- Roles::HasLoudNotification,
- Roles::LastMessage,
- Roles::Timestamp,
- Roles::NotificationCount,
- Qt::DisplayRole,
- });
- });
- connect(
- newRoom.data(), &TimelineModel::roomAvatarUrlChanged, this, [room_id, this]() {
- auto idx = this->roomidToIndex(room_id);
- emit dataChanged(index(idx),
- index(idx),
- {
- Roles::AvatarUrl,
- });
- });
- connect(newRoom.data(), &TimelineModel::roomNameChanged, this, [room_id, this]() {
- auto idx = this->roomidToIndex(room_id);
- emit dataChanged(index(idx),
- index(idx),
- {
- Roles::RoomName,
- });
- });
- connect(
- newRoom.data(), &TimelineModel::notificationsChanged, this, [room_id, this]() {
- auto idx = this->roomidToIndex(room_id);
- emit dataChanged(index(idx),
- index(idx),
- {
- Roles::HasLoudNotification,
- Roles::NotificationCount,
- Qt::DisplayRole,
- });
-
- int total_unread_msgs = 0;
-
- for (const auto &room : models) {
- if (!room.isNull())
- total_unread_msgs += room->notificationCount();
- }
-
- emit totalUnreadMessageCountUpdated(total_unread_msgs);
- });
-
- newRoom->updateLastMessage();
-
- std::vector<QString> previewsToAdd;
- if (newRoom->isSpace()) {
- auto childs = cache::client()->getChildRoomIds(room_id.toStdString());
- for (const auto &c : childs) {
- auto id = QString::fromStdString(c);
- if (!(models.contains(id) || invites.contains(id) ||
- previewedRooms.contains(id))) {
- previewsToAdd.push_back(std::move(id));
- }
- }
- }
+ if (!models.contains(room_id)) {
+ // ensure we get read status updates and are only connected once
+ connect(cache::client(),
+ &Cache::roomReadStatus,
+ this,
+ &RoomlistModel::updateReadStatus,
+ Qt::UniqueConnection);
+
+ QSharedPointer<TimelineModel> newRoom(new TimelineModel(manager, room_id));
+ newRoom->setDecryptDescription(ChatPage::instance()->userSettings()->decryptSidebar());
+
+ connect(newRoom.data(),
+ &TimelineModel::newEncryptedImage,
+ manager->imageProvider(),
+ &MxcImageProvider::addEncryptionInfo);
+ connect(newRoom.data(),
+ &TimelineModel::forwardToRoom,
+ manager,
+ &TimelineViewManager::forwardMessageToRoom);
+ connect(newRoom.data(), &TimelineModel::lastMessageChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::HasLoudNotification,
+ Roles::LastMessage,
+ Roles::Timestamp,
+ Roles::NotificationCount,
+ Qt::DisplayRole,
+ });
+ });
+ connect(newRoom.data(), &TimelineModel::roomAvatarUrlChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::AvatarUrl,
+ });
+ });
+ connect(newRoom.data(), &TimelineModel::roomNameChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::RoomName,
+ });
+ });
+ connect(newRoom.data(), &TimelineModel::notificationsChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::HasLoudNotification,
+ Roles::NotificationCount,
+ Qt::DisplayRole,
+ });
+
+ int total_unread_msgs = 0;
+
+ for (const auto &room : models) {
+ if (!room.isNull())
+ total_unread_msgs += room->notificationCount();
+ }
+
+ emit totalUnreadMessageCountUpdated(total_unread_msgs);
+ });
- bool wasInvite = invites.contains(room_id);
- bool wasPreview = previewedRooms.contains(room_id);
- if (!suppressInsertNotification &&
- ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
- // if the old room was already in the list, don't add it. Also add all
- // previews at the same time.
- beginInsertRows(QModelIndex(),
- (int)roomids.size(),
- (int)(roomids.size() + previewsToAdd.size() -
- ((wasInvite || wasPreview) ? 1 : 0)));
-
- models.insert(room_id, std::move(newRoom));
- if (wasInvite) {
- auto idx = roomidToIndex(room_id);
- invites.remove(room_id);
- emit dataChanged(index(idx), index(idx));
- } else if (wasPreview) {
- auto idx = roomidToIndex(room_id);
- previewedRooms.remove(room_id);
- emit dataChanged(index(idx), index(idx));
- } else {
- roomids.push_back(room_id);
- }
+ newRoom->updateLastMessage();
- if ((wasInvite || wasPreview) && currentRoomPreview_ &&
- currentRoomPreview_->roomid() == room_id) {
- currentRoom_ = models.value(room_id);
- currentRoomPreview_.reset();
- emit currentRoomChanged();
+ std::vector<QString> previewsToAdd;
+ if (newRoom->isSpace()) {
+ auto childs = cache::client()->getChildRoomIds(room_id.toStdString());
+ for (const auto &c : childs) {
+ auto id = QString::fromStdString(c);
+ if (!(models.contains(id) || invites.contains(id) || previewedRooms.contains(id))) {
+ previewsToAdd.push_back(std::move(id));
}
+ }
+ }
- for (auto p : previewsToAdd) {
- previewedRooms.insert(p, std::nullopt);
- roomids.push_back(std::move(p));
- }
+ bool wasInvite = invites.contains(room_id);
+ bool wasPreview = previewedRooms.contains(room_id);
+ if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
+ // if the old room was already in the list, don't add it. Also add all
+ // previews at the same time.
+ beginInsertRows(
+ QModelIndex(),
+ (int)roomids.size(),
+ (int)(roomids.size() + previewsToAdd.size() - ((wasInvite || wasPreview) ? 1 : 0)));
+
+ models.insert(room_id, std::move(newRoom));
+ if (wasInvite) {
+ auto idx = roomidToIndex(room_id);
+ invites.remove(room_id);
+ emit dataChanged(index(idx), index(idx));
+ } else if (wasPreview) {
+ auto idx = roomidToIndex(room_id);
+ previewedRooms.remove(room_id);
+ emit dataChanged(index(idx), index(idx));
+ } else {
+ roomids.push_back(room_id);
+ }
- if (!suppressInsertNotification &&
- ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
- endInsertRows();
+ if ((wasInvite || wasPreview) && currentRoomPreview_ &&
+ currentRoomPreview_->roomid() == room_id) {
+ currentRoom_ = models.value(room_id);
+ currentRoomPreview_.reset();
+ emit currentRoomChanged();
+ }
- emit ChatPage::instance()->newRoom(room_id);
+ for (auto p : previewsToAdd) {
+ previewedRooms.insert(p, std::nullopt);
+ roomids.push_back(std::move(p));
}
+
+ if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
+ endInsertRows();
+
+ emit ChatPage::instance()->newRoom(room_id);
+ }
}
void
RoomlistModel::fetchPreview(QString roomid_) const
{
- std::string roomid = roomid_.toStdString();
- http::client()->get_state_event<mtx::events::state::Create>(
- roomid,
- "",
- [this, roomid](const mtx::events::state::Create &c, mtx::http::RequestErr err) {
- bool is_space = false;
- if (!err) {
- is_space = c.type == mtx::events::state::room_type::space;
- }
-
- http::client()->get_state_event<mtx::events::state::Avatar>(
- roomid,
- "",
- [this, roomid, is_space](const mtx::events::state::Avatar &a,
- mtx::http::RequestErr) {
- auto avatar_url = a.url;
-
- http::client()->get_state_event<mtx::events::state::Topic>(
- roomid,
- "",
- [this, roomid, avatar_url, is_space](
- const mtx::events::state::Topic &t, mtx::http::RequestErr) {
- auto topic = t.topic;
- http::client()->get_state_event<mtx::events::state::Name>(
- roomid,
- "",
- [this, roomid, topic, avatar_url, is_space](
- const mtx::events::state::Name &n,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn(
- "Failed to fetch name event to "
- "create preview for {}",
- roomid);
- }
-
- // don't even add a preview, if we got not a single
- // response
- if (n.name.empty() && avatar_url.empty() &&
- topic.empty())
- return;
-
- RoomInfo info{};
- info.name = n.name;
- info.is_space = is_space;
- info.avatar_url = avatar_url;
- info.topic = topic;
-
- const_cast<RoomlistModel *>(this)->fetchedPreview(
- QString::fromStdString(roomid), info);
- });
- });
- });
- });
+ std::string roomid = roomid_.toStdString();
+ http::client()->get_state_event<mtx::events::state::Create>(
+ roomid, "", [this, roomid](const mtx::events::state::Create &c, mtx::http::RequestErr err) {
+ bool is_space = false;
+ if (!err) {
+ is_space = c.type == mtx::events::state::room_type::space;
+ }
+
+ http::client()->get_state_event<mtx::events::state::Avatar>(
+ roomid,
+ "",
+ [this, roomid, is_space](const mtx::events::state::Avatar &a, mtx::http::RequestErr) {
+ auto avatar_url = a.url;
+
+ http::client()->get_state_event<mtx::events::state::Topic>(
+ roomid,
+ "",
+ [this, roomid, avatar_url, is_space](const mtx::events::state::Topic &t,
+ mtx::http::RequestErr) {
+ auto topic = t.topic;
+ http::client()->get_state_event<mtx::events::state::Name>(
+ roomid,
+ "",
+ [this, roomid, topic, avatar_url, is_space](
+ const mtx::events::state::Name &n, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("Failed to fetch name event to "
+ "create preview for {}",
+ roomid);
+ }
+
+ // don't even add a preview, if we got not a single
+ // response
+ if (n.name.empty() && avatar_url.empty() && topic.empty())
+ return;
+
+ RoomInfo info{};
+ info.name = n.name;
+ info.is_space = is_space;
+ info.avatar_url = avatar_url;
+ info.topic = topic;
+
+ const_cast<RoomlistModel *>(this)->fetchedPreview(
+ QString::fromStdString(roomid), info);
+ });
+ });
+ });
+ });
}
void
RoomlistModel::sync(const mtx::responses::Rooms &rooms)
{
- for (const auto &[room_id, room] : rooms.join) {
- auto qroomid = QString::fromStdString(room_id);
-
- // addRoom will only add the room, if it doesn't exist
- addRoom(qroomid);
- const auto &room_model = models.value(qroomid);
- room_model->sync(room);
- // room_model->addEvents(room.timeline);
- connect(room_model.data(),
- &TimelineModel::newCallEvent,
- manager->callManager(),
- &CallManager::syncEvent,
- Qt::UniqueConnection);
-
- if (ChatPage::instance()->userSettings()->typingNotifications()) {
- for (const auto &ev : room.ephemeral.events) {
- if (auto t = std::get_if<
- mtx::events::EphemeralEvent<mtx::events::ephemeral::Typing>>(
- &ev)) {
- std::vector<QString> typing;
- typing.reserve(t->content.user_ids.size());
- for (const auto &user : t->content.user_ids) {
- if (user != http::client()->user_id().to_string())
- typing.push_back(
- QString::fromStdString(user));
- }
- room_model->updateTypingUsers(typing);
- }
- }
+ for (const auto &[room_id, room] : rooms.join) {
+ auto qroomid = QString::fromStdString(room_id);
+
+ // addRoom will only add the room, if it doesn't exist
+ addRoom(qroomid);
+ const auto &room_model = models.value(qroomid);
+ room_model->sync(room);
+ // room_model->addEvents(room.timeline);
+ connect(room_model.data(),
+ &TimelineModel::newCallEvent,
+ manager->callManager(),
+ &CallManager::syncEvent,
+ Qt::UniqueConnection);
+
+ if (ChatPage::instance()->userSettings()->typingNotifications()) {
+ for (const auto &ev : room.ephemeral.events) {
+ if (auto t =
+ std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Typing>>(
+ &ev)) {
+ std::vector<QString> typing;
+ typing.reserve(t->content.user_ids.size());
+ for (const auto &user : t->content.user_ids) {
+ if (user != http::client()->user_id().to_string())
+ typing.push_back(QString::fromStdString(user));
+ }
+ room_model->updateTypingUsers(typing);
}
+ }
}
-
- for (const auto &[room_id, room] : rooms.leave) {
- (void)room;
- auto qroomid = QString::fromStdString(room_id);
-
- if ((currentRoom_ && currentRoom_->roomId() == qroomid) ||
- (currentRoomPreview_ && currentRoomPreview_->roomid() == qroomid))
- resetCurrentRoom();
-
- auto idx = this->roomidToIndex(qroomid);
- if (idx != -1) {
- beginRemoveRows(QModelIndex(), idx, idx);
- roomids.erase(roomids.begin() + idx);
- if (models.contains(qroomid))
- models.remove(qroomid);
- else if (invites.contains(qroomid))
- invites.remove(qroomid);
- endRemoveRows();
- }
+ }
+
+ for (const auto &[room_id, room] : rooms.leave) {
+ (void)room;
+ auto qroomid = QString::fromStdString(room_id);
+
+ if ((currentRoom_ && currentRoom_->roomId() == qroomid) ||
+ (currentRoomPreview_ && currentRoomPreview_->roomid() == qroomid))
+ resetCurrentRoom();
+
+ auto idx = this->roomidToIndex(qroomid);
+ if (idx != -1) {
+ beginRemoveRows(QModelIndex(), idx, idx);
+ roomids.erase(roomids.begin() + idx);
+ if (models.contains(qroomid))
+ models.remove(qroomid);
+ else if (invites.contains(qroomid))
+ invites.remove(qroomid);
+ endRemoveRows();
}
+ }
- for (const auto &[room_id, room] : rooms.invite) {
- (void)room;
- auto qroomid = QString::fromStdString(room_id);
-
- auto invite = cache::client()->invite(room_id);
- if (!invite)
- continue;
-
- if (invites.contains(qroomid)) {
- invites[qroomid] = *invite;
- auto idx = roomidToIndex(qroomid);
- emit dataChanged(index(idx), index(idx));
- } else {
- beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
- invites.insert(qroomid, *invite);
- roomids.push_back(std::move(qroomid));
- endInsertRows();
- }
+ for (const auto &[room_id, room] : rooms.invite) {
+ (void)room;
+ auto qroomid = QString::fromStdString(room_id);
+
+ auto invite = cache::client()->invite(room_id);
+ if (!invite)
+ continue;
+
+ if (invites.contains(qroomid)) {
+ invites[qroomid] = *invite;
+ auto idx = roomidToIndex(qroomid);
+ emit dataChanged(index(idx), index(idx));
+ } else {
+ beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
+ invites.insert(qroomid, *invite);
+ roomids.push_back(std::move(qroomid));
+ endInsertRows();
}
+ }
}
void
RoomlistModel::initializeRooms()
{
- beginResetModel();
- models.clear();
- roomids.clear();
- invites.clear();
- currentRoom_ = nullptr;
+ beginResetModel();
+ models.clear();
+ roomids.clear();
+ invites.clear();
+ currentRoom_ = nullptr;
- invites = cache::client()->invites();
- for (const auto &id : invites.keys())
- roomids.push_back(id);
+ invites = cache::client()->invites();
+ for (const auto &id : invites.keys())
+ roomids.push_back(id);
- for (const auto &id : cache::client()->roomIds())
- addRoom(id, true);
+ for (const auto &id : cache::client()->roomIds())
+ addRoom(id, true);
- nhlog::db()->info("Restored {} rooms from cache", rowCount());
+ nhlog::db()->info("Restored {} rooms from cache", rowCount());
- endResetModel();
+ endResetModel();
}
void
RoomlistModel::clear()
{
- beginResetModel();
- models.clear();
- invites.clear();
- roomids.clear();
- currentRoom_ = nullptr;
- emit currentRoomChanged();
- endResetModel();
+ beginResetModel();
+ models.clear();
+ invites.clear();
+ roomids.clear();
+ currentRoom_ = nullptr;
+ emit currentRoomChanged();
+ endResetModel();
}
void
RoomlistModel::joinPreview(QString roomid, QString parentSpace)
{
- if (previewedRooms.contains(roomid)) {
- auto child = cache::client()->getStateEvent<mtx::events::state::space::Child>(
- parentSpace.toStdString(), roomid.toStdString());
- ChatPage::instance()->joinRoomVia(roomid.toStdString(),
- (child && child->content.via)
- ? child->content.via.value()
- : std::vector<std::string>{},
- false);
- }
+ if (previewedRooms.contains(roomid)) {
+ auto child = cache::client()->getStateEvent<mtx::events::state::space::Child>(
+ parentSpace.toStdString(), roomid.toStdString());
+ ChatPage::instance()->joinRoomVia(
+ roomid.toStdString(),
+ (child && child->content.via) ? child->content.via.value() : std::vector<std::string>{},
+ false);
+ }
}
void
RoomlistModel::acceptInvite(QString roomid)
{
- if (invites.contains(roomid)) {
- // Don't remove invite yet, so that we can switch to it
- ChatPage::instance()->joinRoom(roomid);
- }
+ if (invites.contains(roomid)) {
+ // Don't remove invite yet, so that we can switch to it
+ ChatPage::instance()->joinRoom(roomid);
+ }
}
void
RoomlistModel::declineInvite(QString roomid)
{
- if (invites.contains(roomid)) {
- auto idx = roomidToIndex(roomid);
-
- if (idx != -1) {
- beginRemoveRows(QModelIndex(), idx, idx);
- roomids.erase(roomids.begin() + idx);
- invites.remove(roomid);
- endRemoveRows();
- ChatPage::instance()->leaveRoom(roomid);
- }
+ if (invites.contains(roomid)) {
+ auto idx = roomidToIndex(roomid);
+
+ if (idx != -1) {
+ beginRemoveRows(QModelIndex(), idx, idx);
+ roomids.erase(roomids.begin() + idx);
+ invites.remove(roomid);
+ endRemoveRows();
+ ChatPage::instance()->leaveRoom(roomid);
}
+ }
}
void
RoomlistModel::leave(QString roomid)
{
- if (models.contains(roomid)) {
- auto idx = roomidToIndex(roomid);
-
- if (idx != -1) {
- beginRemoveRows(QModelIndex(), idx, idx);
- roomids.erase(roomids.begin() + idx);
- models.remove(roomid);
- endRemoveRows();
- ChatPage::instance()->leaveRoom(roomid);
- }
+ if (models.contains(roomid)) {
+ auto idx = roomidToIndex(roomid);
+
+ if (idx != -1) {
+ beginRemoveRows(QModelIndex(), idx, idx);
+ roomids.erase(roomids.begin() + idx);
+ models.remove(roomid);
+ endRemoveRows();
+ ChatPage::instance()->leaveRoom(roomid);
}
+ }
}
void
RoomlistModel::setCurrentRoom(QString roomid)
{
- if ((currentRoom_ && currentRoom_->roomId() == roomid) ||
- (currentRoomPreview_ && currentRoomPreview_->roomid() == roomid))
- return;
+ if ((currentRoom_ && currentRoom_->roomId() == roomid) ||
+ (currentRoomPreview_ && currentRoomPreview_->roomid() == roomid))
+ return;
+
+ nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString());
+ if (models.contains(roomid)) {
+ currentRoom_ = models.value(roomid);
+ currentRoomPreview_.reset();
+ emit currentRoomChanged();
+ nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
+ } else if (invites.contains(roomid) || previewedRooms.contains(roomid)) {
+ currentRoom_ = nullptr;
+ std::optional<RoomInfo> i;
- nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString());
- if (models.contains(roomid)) {
- currentRoom_ = models.value(roomid);
- currentRoomPreview_.reset();
- emit currentRoomChanged();
- nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
- } else if (invites.contains(roomid) || previewedRooms.contains(roomid)) {
- currentRoom_ = nullptr;
- std::optional<RoomInfo> i;
-
- RoomPreview p;
-
- if (invites.contains(roomid)) {
- i = invites.value(roomid);
- p.isInvite_ = true;
- } else {
- i = previewedRooms.value(roomid);
- p.isInvite_ = false;
- }
+ RoomPreview p;
- if (i) {
- p.roomid_ = roomid;
- p.roomName_ = QString::fromStdString(i->name);
- p.roomTopic_ = QString::fromStdString(i->topic);
- p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url);
- currentRoomPreview_ = std::move(p);
- }
+ if (invites.contains(roomid)) {
+ i = invites.value(roomid);
+ p.isInvite_ = true;
+ } else {
+ i = previewedRooms.value(roomid);
+ p.isInvite_ = false;
+ }
- emit currentRoomChanged();
- nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
+ if (i) {
+ p.roomid_ = roomid;
+ p.roomName_ = QString::fromStdString(i->name);
+ p.roomTopic_ = QString::fromStdString(i->topic);
+ p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url);
+ currentRoomPreview_ = std::move(p);
}
+
+ emit currentRoomChanged();
+ nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
+ }
}
namespace {
enum NotificationImportance : short
{
- ImportanceDisabled = -3,
- NoPreview = -2,
- Preview = -1,
- AllEventsRead = 0,
- NewMessage = 1,
- NewMentions = 2,
- Invite = 3,
- SubSpace = 4,
- CurrentSpace = 5,
+ ImportanceDisabled = -3,
+ NoPreview = -2,
+ Preview = -1,
+ AllEventsRead = 0,
+ NewMessage = 1,
+ NewMentions = 2,
+ Invite = 3,
+ SubSpace = 4,
+ CurrentSpace = 5,
};
}
short int
FilteredRoomlistModel::calculateImportance(const QModelIndex &idx) const
{
- // Returns the degree of importance of the unread messages in the room.
- // If sorting by importance is disabled in settings, this only ever
- // returns ImportanceDisabled or Invite
- if (sourceModel()->data(idx, RoomlistModel::IsSpace).toBool()) {
- if (filterType == FilterBy::Space &&
- filterStr == sourceModel()->data(idx, RoomlistModel::RoomId).toString())
- return CurrentSpace;
- else
- return SubSpace;
- } else if (sourceModel()->data(idx, RoomlistModel::IsPreview).toBool()) {
- if (sourceModel()->data(idx, RoomlistModel::IsPreviewFetched).toBool())
- return Preview;
- else
- return NoPreview;
- } else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) {
- return Invite;
- } else if (!this->sortByImportance) {
- return ImportanceDisabled;
- } else if (sourceModel()->data(idx, RoomlistModel::HasLoudNotification).toBool()) {
- return NewMentions;
- } else if (sourceModel()->data(idx, RoomlistModel::NotificationCount).toInt() > 0) {
- return NewMessage;
- } else {
- return AllEventsRead;
- }
+ // Returns the degree of importance of the unread messages in the room.
+ // If sorting by importance is disabled in settings, this only ever
+ // returns ImportanceDisabled or Invite
+ if (sourceModel()->data(idx, RoomlistModel::IsSpace).toBool()) {
+ if (filterType == FilterBy::Space &&
+ filterStr == sourceModel()->data(idx, RoomlistModel::RoomId).toString())
+ return CurrentSpace;
+ else
+ return SubSpace;
+ } else if (sourceModel()->data(idx, RoomlistModel::IsPreview).toBool()) {
+ if (sourceModel()->data(idx, RoomlistModel::IsPreviewFetched).toBool())
+ return Preview;
+ else
+ return NoPreview;
+ } else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) {
+ return Invite;
+ } else if (!this->sortByImportance) {
+ return ImportanceDisabled;
+ } else if (sourceModel()->data(idx, RoomlistModel::HasLoudNotification).toBool()) {
+ return NewMentions;
+ } else if (sourceModel()->data(idx, RoomlistModel::NotificationCount).toInt() > 0) {
+ return NewMessage;
+ } else {
+ return AllEventsRead;
+ }
}
bool
FilteredRoomlistModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
- QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex());
- QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex());
-
- // Sort by "importance" (i.e. invites before mentions before
- // notifs before new events before old events), then secondly
- // by recency.
-
- // Checking importance first
- const auto a_importance = calculateImportance(left_idx);
- const auto b_importance = calculateImportance(right_idx);
- if (a_importance != b_importance) {
- return a_importance > b_importance;
- }
-
- // Now sort by recency
- // Zero if empty, otherwise the time that the event occured
- uint64_t a_recency = sourceModel()->data(left_idx, RoomlistModel::Timestamp).toULongLong();
- uint64_t b_recency = sourceModel()->data(right_idx, RoomlistModel::Timestamp).toULongLong();
-
- if (a_recency != b_recency)
- return a_recency > b_recency;
- else
- return left.row() < right.row();
+ QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex());
+ QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex());
+
+ // Sort by "importance" (i.e. invites before mentions before
+ // notifs before new events before old events), then secondly
+ // by recency.
+
+ // Checking importance first
+ const auto a_importance = calculateImportance(left_idx);
+ const auto b_importance = calculateImportance(right_idx);
+ if (a_importance != b_importance) {
+ return a_importance > b_importance;
+ }
+
+ // Now sort by recency
+ // Zero if empty, otherwise the time that the event occured
+ uint64_t a_recency = sourceModel()->data(left_idx, RoomlistModel::Timestamp).toULongLong();
+ uint64_t b_recency = sourceModel()->data(right_idx, RoomlistModel::Timestamp).toULongLong();
+
+ if (a_recency != b_recency)
+ return a_recency > b_recency;
+ else
+ return left.row() < right.row();
}
FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *parent)
: QSortFilterProxyModel(parent)
, roomlistmodel(model)
{
- this->sortByImportance = UserSettings::instance()->sortByImportance();
- setSourceModel(model);
- setDynamicSortFilter(true);
-
- QObject::connect(UserSettings::instance().get(),
- &UserSettings::roomSortingChanged,
- this,
- [this](bool sortByImportance_) {
- this->sortByImportance = sortByImportance_;
- invalidate();
- });
-
- connect(roomlistmodel,
- &RoomlistModel::currentRoomChanged,
- this,
- &FilteredRoomlistModel::currentRoomChanged);
-
- sort(0);
+ this->sortByImportance = UserSettings::instance()->sortByImportance();
+ setSourceModel(model);
+ setDynamicSortFilter(true);
+
+ QObject::connect(UserSettings::instance().get(),
+ &UserSettings::roomSortingChanged,
+ this,
+ [this](bool sortByImportance_) {
+ this->sortByImportance = sortByImportance_;
+ invalidate();
+ });
+
+ connect(roomlistmodel,
+ &RoomlistModel::currentRoomChanged,
+ this,
+ &FilteredRoomlistModel::currentRoomChanged);
+
+ sort(0);
}
void
FilteredRoomlistModel::updateHiddenTagsAndSpaces()
{
- hiddenTags.clear();
- hiddenSpaces.clear();
- for (const auto &t : UserSettings::instance()->hiddenTags()) {
- if (t.startsWith("tag:"))
- hiddenTags.push_back(t.mid(4));
- else if (t.startsWith("space:"))
- hiddenSpaces.push_back(t.mid(6));
- }
-
- invalidateFilter();
+ hiddenTags.clear();
+ hiddenSpaces.clear();
+ for (const auto &t : UserSettings::instance()->hiddenTags()) {
+ if (t.startsWith("tag:"))
+ hiddenTags.push_back(t.mid(4));
+ else if (t.startsWith("space:"))
+ hiddenSpaces.push_back(t.mid(6));
+ }
+
+ invalidateFilter();
}
bool
FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const
{
- if (filterType == FilterBy::Nothing) {
- if (sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
- .toBool()) {
- return false;
- }
+ if (filterType == FilterBy::Nothing) {
+ if (sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
+ .toBool()) {
+ return false;
+ }
- if (sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
- .toBool()) {
- return false;
- }
+ if (sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
+ .toBool()) {
+ return false;
+ }
- if (!hiddenTags.empty()) {
- auto tags =
- sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
- .toStringList();
+ if (!hiddenTags.empty()) {
+ auto tags = sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
+ .toStringList();
- for (const auto &t : tags)
- if (hiddenTags.contains(t))
- return false;
- }
+ for (const auto &t : tags)
+ if (hiddenTags.contains(t))
+ return false;
+ }
- if (!hiddenSpaces.empty()) {
- auto parents =
- sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
- .toStringList();
- for (const auto &t : parents)
- if (hiddenSpaces.contains(t))
- return false;
- }
+ if (!hiddenSpaces.empty()) {
+ auto parents = sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
+ .toStringList();
+ for (const auto &t : parents)
+ if (hiddenSpaces.contains(t))
+ return false;
+ }
- return true;
- } else if (filterType == FilterBy::Tag) {
- if (sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
- .toBool()) {
- return false;
- }
+ return true;
+ } else if (filterType == FilterBy::Tag) {
+ if (sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
+ .toBool()) {
+ return false;
+ }
- if (sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
- .toBool()) {
- return false;
- }
+ if (sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
+ .toBool()) {
+ return false;
+ }
- auto tags = sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
- .toStringList();
+ auto tags = sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
+ .toStringList();
- if (!tags.contains(filterStr))
- return false;
+ if (!tags.contains(filterStr))
+ return false;
- if (!hiddenTags.empty()) {
- for (const auto &t : tags)
- if (t != filterStr && hiddenTags.contains(t))
- return false;
- }
+ if (!hiddenTags.empty()) {
+ for (const auto &t : tags)
+ if (t != filterStr && hiddenTags.contains(t))
+ return false;
+ }
- if (!hiddenSpaces.empty()) {
- auto parents =
- sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
- .toStringList();
- for (const auto &t : parents)
- if (hiddenSpaces.contains(t))
- return false;
- }
+ if (!hiddenSpaces.empty()) {
+ auto parents = sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
+ .toStringList();
+ for (const auto &t : parents)
+ if (hiddenSpaces.contains(t))
+ return false;
+ }
- return true;
- } else if (filterType == FilterBy::Space) {
- if (filterStr == sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::RoomId)
- .toString())
- return true;
-
- auto parents =
- sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
- .toStringList();
-
- if (!parents.contains(filterStr))
- return false;
-
- if (!hiddenTags.empty()) {
- auto tags =
- sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
- .toStringList();
-
- for (const auto &t : tags)
- if (hiddenTags.contains(t))
- return false;
- }
+ return true;
+ } else if (filterType == FilterBy::Space) {
+ if (filterStr == sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::RoomId)
+ .toString())
+ return true;
- if (!hiddenSpaces.empty()) {
- for (const auto &t : parents)
- if (hiddenSpaces.contains(t))
- return false;
- }
+ auto parents = sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
+ .toStringList();
- if (sourceModel()
- ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
- .toBool() &&
- !parents.contains(filterStr)) {
- return false;
- }
+ if (!parents.contains(filterStr))
+ return false;
- return true;
- } else {
- return true;
+ if (!hiddenTags.empty()) {
+ auto tags = sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
+ .toStringList();
+
+ for (const auto &t : tags)
+ if (hiddenTags.contains(t))
+ return false;
+ }
+
+ if (!hiddenSpaces.empty()) {
+ for (const auto &t : parents)
+ if (hiddenSpaces.contains(t))
+ return false;
+ }
+
+ if (sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
+ .toBool() &&
+ !parents.contains(filterStr)) {
+ return false;
}
+
+ return true;
+ } else {
+ return true;
+ }
}
void
FilteredRoomlistModel::toggleTag(QString roomid, QString tag, bool on)
{
- if (on) {
- http::client()->put_tag(
- roomid.toStdString(), tag.toStdString(), {}, [tag](mtx::http::RequestErr err) {
- if (err) {
- nhlog::ui()->error("Failed to add tag: {}, {}",
- tag.toStdString(),
- err->matrix_error.error);
- }
- });
- } else {
- http::client()->delete_tag(
- roomid.toStdString(), tag.toStdString(), [tag](mtx::http::RequestErr err) {
- if (err) {
- nhlog::ui()->error("Failed to delete tag: {}, {}",
- tag.toStdString(),
- err->matrix_error.error);
- }
- });
- }
+ if (on) {
+ http::client()->put_tag(
+ roomid.toStdString(), tag.toStdString(), {}, [tag](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::ui()->error(
+ "Failed to add tag: {}, {}", tag.toStdString(), err->matrix_error.error);
+ }
+ });
+ } else {
+ http::client()->delete_tag(
+ roomid.toStdString(), tag.toStdString(), [tag](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::ui()->error(
+ "Failed to delete tag: {}, {}", tag.toStdString(), err->matrix_error.error);
+ }
+ });
+ }
}
void
FilteredRoomlistModel::nextRoomWithActivity()
{
- int roomWithMention = -1;
- int roomWithNotification = -1;
- int roomWithUnreadMessage = -1;
- auto r = currentRoom();
- int currentRoomIdx = r ? roomidToIndex(r->roomId()) : -1;
- // first look for mentions
- for (int i = 0; i < (int)roomlistmodel->roomids.size(); i++) {
- if (i == currentRoomIdx)
- continue;
- if (this->data(index(i, 0), RoomlistModel::HasLoudNotification).toBool()) {
- roomWithMention = i;
- break;
- }
- if (roomWithNotification == -1 &&
- this->data(index(i, 0), RoomlistModel::NotificationCount).toInt() > 0) {
- roomWithNotification = i;
- // don't break, we must continue looking for rooms with mentions
- }
- if (roomWithNotification == -1 && roomWithUnreadMessage == -1 &&
- this->data(index(i, 0), RoomlistModel::HasUnreadMessages).toBool()) {
- roomWithUnreadMessage = i;
- // don't break, we must continue looking for rooms with mentions
- }
+ int roomWithMention = -1;
+ int roomWithNotification = -1;
+ int roomWithUnreadMessage = -1;
+ auto r = currentRoom();
+ int currentRoomIdx = r ? roomidToIndex(r->roomId()) : -1;
+ // first look for mentions
+ for (int i = 0; i < (int)roomlistmodel->roomids.size(); i++) {
+ if (i == currentRoomIdx)
+ continue;
+ if (this->data(index(i, 0), RoomlistModel::HasLoudNotification).toBool()) {
+ roomWithMention = i;
+ break;
}
- QString targetRoomId = nullptr;
- if (roomWithMention != -1) {
- targetRoomId =
- this->data(index(roomWithMention, 0), RoomlistModel::RoomId).toString();
- nhlog::ui()->debug("choosing {} for mentions", targetRoomId.toStdString());
- } else if (roomWithNotification != -1) {
- targetRoomId =
- this->data(index(roomWithNotification, 0), RoomlistModel::RoomId).toString();
- nhlog::ui()->debug("choosing {} for notifications", targetRoomId.toStdString());
- } else if (roomWithUnreadMessage != -1) {
- targetRoomId =
- this->data(index(roomWithUnreadMessage, 0), RoomlistModel::RoomId).toString();
- nhlog::ui()->debug("choosing {} for unread messages", targetRoomId.toStdString());
+ if (roomWithNotification == -1 &&
+ this->data(index(i, 0), RoomlistModel::NotificationCount).toInt() > 0) {
+ roomWithNotification = i;
+ // don't break, we must continue looking for rooms with mentions
}
- if (targetRoomId != nullptr) {
- setCurrentRoom(targetRoomId);
+ if (roomWithNotification == -1 && roomWithUnreadMessage == -1 &&
+ this->data(index(i, 0), RoomlistModel::HasUnreadMessages).toBool()) {
+ roomWithUnreadMessage = i;
+ // don't break, we must continue looking for rooms with mentions
}
+ }
+ QString targetRoomId = nullptr;
+ if (roomWithMention != -1) {
+ targetRoomId = this->data(index(roomWithMention, 0), RoomlistModel::RoomId).toString();
+ nhlog::ui()->debug("choosing {} for mentions", targetRoomId.toStdString());
+ } else if (roomWithNotification != -1) {
+ targetRoomId = this->data(index(roomWithNotification, 0), RoomlistModel::RoomId).toString();
+ nhlog::ui()->debug("choosing {} for notifications", targetRoomId.toStdString());
+ } else if (roomWithUnreadMessage != -1) {
+ targetRoomId =
+ this->data(index(roomWithUnreadMessage, 0), RoomlistModel::RoomId).toString();
+ nhlog::ui()->debug("choosing {} for unread messages", targetRoomId.toStdString());
+ }
+ if (targetRoomId != nullptr) {
+ setCurrentRoom(targetRoomId);
+ }
}
void
FilteredRoomlistModel::nextRoom()
{
- auto r = currentRoom();
-
- if (r) {
- int idx = roomidToIndex(r->roomId());
- idx++;
- if (idx < rowCount()) {
- setCurrentRoom(
- data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
- }
+ auto r = currentRoom();
+
+ if (r) {
+ int idx = roomidToIndex(r->roomId());
+ idx++;
+ if (idx < rowCount()) {
+ setCurrentRoom(data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
}
+ }
}
void
FilteredRoomlistModel::previousRoom()
{
- auto r = currentRoom();
-
- if (r) {
- int idx = roomidToIndex(r->roomId());
- idx--;
- if (idx >= 0) {
- setCurrentRoom(
- data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
- }
+ auto r = currentRoom();
+
+ if (r) {
+ int idx = roomidToIndex(r->roomId());
+ idx--;
+ if (idx >= 0) {
+ setCurrentRoom(data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
}
+ }
}
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 27c14bec..458e0fe7 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -20,195 +20,191 @@ class TimelineViewManager;
class RoomPreview
{
- Q_GADGET
- Q_PROPERTY(QString roomid READ roomid CONSTANT)
- Q_PROPERTY(QString roomName READ roomName CONSTANT)
- Q_PROPERTY(QString roomTopic READ roomTopic CONSTANT)
- Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl CONSTANT)
- Q_PROPERTY(bool isInvite READ isInvite CONSTANT)
+ Q_GADGET
+ Q_PROPERTY(QString roomid READ roomid CONSTANT)
+ Q_PROPERTY(QString roomName READ roomName CONSTANT)
+ Q_PROPERTY(QString roomTopic READ roomTopic CONSTANT)
+ Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl CONSTANT)
+ Q_PROPERTY(bool isInvite READ isInvite CONSTANT)
public:
- RoomPreview() {}
+ RoomPreview() {}
- QString roomid() const { return roomid_; }
- QString roomName() const { return roomName_; }
- QString roomTopic() const { return roomTopic_; }
- QString roomAvatarUrl() const { return roomAvatarUrl_; }
- bool isInvite() const { return isInvite_; }
+ QString roomid() const { return roomid_; }
+ QString roomName() const { return roomName_; }
+ QString roomTopic() const { return roomTopic_; }
+ QString roomAvatarUrl() const { return roomAvatarUrl_; }
+ bool isInvite() const { return isInvite_; }
- QString roomid_, roomName_, roomAvatarUrl_, roomTopic_;
- bool isInvite_ = false;
+ QString roomid_, roomName_, roomAvatarUrl_, roomTopic_;
+ bool isInvite_ = false;
};
class RoomlistModel : public QAbstractListModel
{
- Q_OBJECT
- Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET
- resetCurrentRoom)
- Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
- RESET resetCurrentRoom)
+ Q_OBJECT
+ Q_PROPERTY(
+ TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET resetCurrentRoom)
+ Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
+ RESET resetCurrentRoom)
public:
- enum Roles
- {
- AvatarUrl = Qt::UserRole,
- RoomName,
- RoomId,
- LastMessage,
- Time,
- Timestamp,
- HasUnreadMessages,
- HasLoudNotification,
- NotificationCount,
- IsInvite,
- IsSpace,
- IsPreview,
- IsPreviewFetched,
- Tags,
- ParentSpaces,
- IsDirect,
- DirectChatOtherUserId,
- };
-
- RoomlistModel(TimelineViewManager *parent = nullptr);
- QHash<int, QByteArray> 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;
- QSharedPointer<TimelineModel> getRoomById(QString id) const
- {
- if (models.contains(id))
- return models.value(id);
- else
- return {};
- }
+ enum Roles
+ {
+ AvatarUrl = Qt::UserRole,
+ RoomName,
+ RoomId,
+ LastMessage,
+ Time,
+ Timestamp,
+ HasUnreadMessages,
+ HasLoudNotification,
+ NotificationCount,
+ IsInvite,
+ IsSpace,
+ IsPreview,
+ IsPreviewFetched,
+ Tags,
+ ParentSpaces,
+ IsDirect,
+ DirectChatOtherUserId,
+ };
+
+ RoomlistModel(TimelineViewManager *parent = nullptr);
+ QHash<int, QByteArray> 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;
+ QSharedPointer<TimelineModel> getRoomById(QString id) const
+ {
+ if (models.contains(id))
+ return models.value(id);
+ else
+ return {};
+ }
public slots:
- void initializeRooms();
- void sync(const mtx::responses::Rooms &rooms);
- void clear();
- int roomidToIndex(QString roomid)
- {
- for (int i = 0; i < (int)roomids.size(); i++) {
- if (roomids[i] == roomid)
- return i;
- }
-
- return -1;
- }
- void joinPreview(QString roomid, QString parentSpace);
- void acceptInvite(QString roomid);
- void declineInvite(QString roomid);
- void leave(QString roomid);
- TimelineModel *currentRoom() const { return currentRoom_.get(); }
- RoomPreview currentRoomPreview() const
- {
- return currentRoomPreview_.value_or(RoomPreview{});
- }
- void setCurrentRoom(QString roomid);
- void resetCurrentRoom()
- {
- currentRoom_ = nullptr;
- currentRoomPreview_.reset();
- emit currentRoomChanged();
+ void initializeRooms();
+ void sync(const mtx::responses::Rooms &rooms);
+ void clear();
+ int roomidToIndex(QString roomid)
+ {
+ for (int i = 0; i < (int)roomids.size(); i++) {
+ if (roomids[i] == roomid)
+ return i;
}
+ return -1;
+ }
+ void joinPreview(QString roomid, QString parentSpace);
+ void acceptInvite(QString roomid);
+ void declineInvite(QString roomid);
+ void leave(QString roomid);
+ TimelineModel *currentRoom() const { return currentRoom_.get(); }
+ RoomPreview currentRoomPreview() const { return currentRoomPreview_.value_or(RoomPreview{}); }
+ void setCurrentRoom(QString roomid);
+ void resetCurrentRoom()
+ {
+ currentRoom_ = nullptr;
+ currentRoomPreview_.reset();
+ emit currentRoomChanged();
+ }
+
private slots:
- void updateReadStatus(const std::map<QString, bool> roomReadStatus_);
+ void updateReadStatus(const std::map<QString, bool> roomReadStatus_);
signals:
- void totalUnreadMessageCountUpdated(int unreadMessages);
- void currentRoomChanged();
- void fetchedPreview(QString roomid, RoomInfo info);
+ void totalUnreadMessageCountUpdated(int unreadMessages);
+ void currentRoomChanged();
+ void fetchedPreview(QString roomid, RoomInfo info);
private:
- void addRoom(const QString &room_id, bool suppressInsertNotification = false);
- void fetchPreview(QString roomid) const;
+ void addRoom(const QString &room_id, bool suppressInsertNotification = false);
+ void fetchPreview(QString roomid) const;
- TimelineViewManager *manager = nullptr;
- std::vector<QString> roomids;
- QHash<QString, RoomInfo> invites;
- QHash<QString, QSharedPointer<TimelineModel>> models;
- std::map<QString, bool> roomReadStatus;
- QHash<QString, std::optional<RoomInfo>> previewedRooms;
+ TimelineViewManager *manager = nullptr;
+ std::vector<QString> roomids;
+ QHash<QString, RoomInfo> invites;
+ QHash<QString, QSharedPointer<TimelineModel>> models;
+ std::map<QString, bool> roomReadStatus;
+ QHash<QString, std::optional<RoomInfo>> previewedRooms;
- QSharedPointer<TimelineModel> currentRoom_;
- std::optional<RoomPreview> currentRoomPreview_;
+ QSharedPointer<TimelineModel> currentRoom_;
+ std::optional<RoomPreview> currentRoomPreview_;
- friend class FilteredRoomlistModel;
+ friend class FilteredRoomlistModel;
};
class FilteredRoomlistModel : public QSortFilterProxyModel
{
- Q_OBJECT
- Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET
- resetCurrentRoom)
- Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
- RESET resetCurrentRoom)
+ Q_OBJECT
+ Q_PROPERTY(
+ TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET resetCurrentRoom)
+ Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
+ RESET resetCurrentRoom)
public:
- FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr);
- bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
- bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
+ FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr);
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
public slots:
- int roomidToIndex(QString roomid)
- {
- return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid)))
- .row();
- }
- void joinPreview(QString roomid)
- {
- roomlistmodel->joinPreview(roomid, filterType == FilterBy::Space ? filterStr : "");
- }
- void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); }
- void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); }
- void leave(QString roomid) { roomlistmodel->leave(roomid); }
- void toggleTag(QString roomid, QString tag, bool on);
-
- TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); }
- RoomPreview currentRoomPreview() const { return roomlistmodel->currentRoomPreview(); }
- void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); }
- void resetCurrentRoom() { roomlistmodel->resetCurrentRoom(); }
-
- void nextRoomWithActivity();
- void nextRoom();
- void previousRoom();
-
- void updateFilterTag(QString tagId)
- {
- if (tagId.startsWith("tag:")) {
- filterType = FilterBy::Tag;
- filterStr = tagId.mid(4);
- } else if (tagId.startsWith("space:")) {
- filterType = FilterBy::Space;
- filterStr = tagId.mid(6);
- } else {
- filterType = FilterBy::Nothing;
- filterStr.clear();
- }
-
- invalidateFilter();
+ int roomidToIndex(QString roomid)
+ {
+ return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid))).row();
+ }
+ void joinPreview(QString roomid)
+ {
+ roomlistmodel->joinPreview(roomid, filterType == FilterBy::Space ? filterStr : "");
+ }
+ void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); }
+ void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); }
+ void leave(QString roomid) { roomlistmodel->leave(roomid); }
+ void toggleTag(QString roomid, QString tag, bool on);
+
+ TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); }
+ RoomPreview currentRoomPreview() const { return roomlistmodel->currentRoomPreview(); }
+ void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); }
+ void resetCurrentRoom() { roomlistmodel->resetCurrentRoom(); }
+
+ void nextRoomWithActivity();
+ void nextRoom();
+ void previousRoom();
+
+ void updateFilterTag(QString tagId)
+ {
+ if (tagId.startsWith("tag:")) {
+ filterType = FilterBy::Tag;
+ filterStr = tagId.mid(4);
+ } else if (tagId.startsWith("space:")) {
+ filterType = FilterBy::Space;
+ filterStr = tagId.mid(6);
+ } else {
+ filterType = FilterBy::Nothing;
+ filterStr.clear();
}
- void updateHiddenTagsAndSpaces();
+ invalidateFilter();
+ }
+
+ void updateHiddenTagsAndSpaces();
signals:
- void currentRoomChanged();
+ void currentRoomChanged();
private:
- short int calculateImportance(const QModelIndex &idx) const;
- RoomlistModel *roomlistmodel;
- bool sortByImportance = true;
-
- enum class FilterBy
- {
- Tag,
- Space,
- Nothing,
- };
- QString filterStr = "";
- FilterBy filterType = FilterBy::Nothing;
- QStringList hiddenTags, hiddenSpaces;
+ short int calculateImportance(const QModelIndex &idx) const;
+ RoomlistModel *roomlistmodel;
+ bool sortByImportance = true;
+
+ enum class FilterBy
+ {
+ Tag,
+ Space,
+ Nothing,
+ };
+ QString filterStr = "";
+ FilterBy filterType = FilterBy::Nothing;
+ QStringList hiddenTags, hiddenSpaces;
};
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 00f6d9df..720a78fe 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -38,288 +38,285 @@ namespace std {
inline uint
qHash(const std::string &key, uint seed = 0)
{
- return qHash(QByteArray::fromRawData(key.data(), (int)key.length()), seed);
+ return qHash(QByteArray::fromRawData(key.data(), (int)key.length()), seed);
}
}
namespace {
struct RoomEventType
{
- template<class T>
- qml_mtx_events::EventType operator()(const mtx::events::Event<T> &e)
- {
- using mtx::events::EventType;
- switch (e.type) {
- case EventType::RoomKeyRequest:
- return qml_mtx_events::EventType::KeyRequest;
- case EventType::Reaction:
- return qml_mtx_events::EventType::Reaction;
- case EventType::RoomAliases:
- return qml_mtx_events::EventType::Aliases;
- case EventType::RoomAvatar:
- return qml_mtx_events::EventType::Avatar;
- case EventType::RoomCanonicalAlias:
- return qml_mtx_events::EventType::CanonicalAlias;
- case EventType::RoomCreate:
- return qml_mtx_events::EventType::RoomCreate;
- case EventType::RoomEncrypted:
- return qml_mtx_events::EventType::Encrypted;
- case EventType::RoomEncryption:
- return qml_mtx_events::EventType::Encryption;
- case EventType::RoomGuestAccess:
- return qml_mtx_events::EventType::RoomGuestAccess;
- case EventType::RoomHistoryVisibility:
- return qml_mtx_events::EventType::RoomHistoryVisibility;
- case EventType::RoomJoinRules:
- return qml_mtx_events::EventType::RoomJoinRules;
- case EventType::RoomMember:
- return qml_mtx_events::EventType::Member;
- case EventType::RoomMessage:
- return qml_mtx_events::EventType::UnknownMessage;
- case EventType::RoomName:
- return qml_mtx_events::EventType::Name;
- case EventType::RoomPowerLevels:
- return qml_mtx_events::EventType::PowerLevels;
- case EventType::RoomTopic:
- return qml_mtx_events::EventType::Topic;
- case EventType::RoomTombstone:
- return qml_mtx_events::EventType::Tombstone;
- case EventType::RoomRedaction:
- return qml_mtx_events::EventType::Redaction;
- case EventType::RoomPinnedEvents:
- return qml_mtx_events::EventType::PinnedEvents;
- case EventType::Sticker:
- return qml_mtx_events::EventType::Sticker;
- case EventType::Tag:
- return qml_mtx_events::EventType::Tag;
- case EventType::Unsupported:
- return qml_mtx_events::EventType::Unsupported;
- default:
- return qml_mtx_events::EventType::UnknownMessage;
- }
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Audio> &)
- {
- return qml_mtx_events::EventType::AudioMessage;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &)
- {
- return qml_mtx_events::EventType::EmoteMessage;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::File> &)
- {
- return qml_mtx_events::EventType::FileMessage;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Image> &)
- {
- return qml_mtx_events::EventType::ImageMessage;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Notice> &)
- {
- return qml_mtx_events::EventType::NoticeMessage;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Text> &)
- {
- return qml_mtx_events::EventType::TextMessage;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Video> &)
- {
- return qml_mtx_events::EventType::VideoMessage;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationRequest> &)
- {
- return qml_mtx_events::EventType::KeyVerificationRequest;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationStart> &)
- {
- return qml_mtx_events::EventType::KeyVerificationStart;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationMac> &)
- {
- return qml_mtx_events::EventType::KeyVerificationMac;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationAccept> &)
- {
- return qml_mtx_events::EventType::KeyVerificationAccept;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationReady> &)
- {
- return qml_mtx_events::EventType::KeyVerificationReady;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationCancel> &)
- {
- return qml_mtx_events::EventType::KeyVerificationCancel;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationKey> &)
- {
- return qml_mtx_events::EventType::KeyVerificationKey;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::KeyVerificationDone> &)
- {
- return qml_mtx_events::EventType::KeyVerificationDone;
- }
- qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Redacted> &)
- {
- return qml_mtx_events::EventType::Redacted;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::CallInvite> &)
- {
- return qml_mtx_events::EventType::CallInvite;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::CallAnswer> &)
- {
- return qml_mtx_events::EventType::CallAnswer;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::CallHangUp> &)
- {
- return qml_mtx_events::EventType::CallHangUp;
- }
- qml_mtx_events::EventType operator()(
- const mtx::events::Event<mtx::events::msg::CallCandidates> &)
- {
- return qml_mtx_events::EventType::CallCandidates;
- }
- // ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return
- // ::EventType::LocationMessage; }
+ template<class T>
+ qml_mtx_events::EventType operator()(const mtx::events::Event<T> &e)
+ {
+ using mtx::events::EventType;
+ switch (e.type) {
+ case EventType::RoomKeyRequest:
+ return qml_mtx_events::EventType::KeyRequest;
+ case EventType::Reaction:
+ return qml_mtx_events::EventType::Reaction;
+ case EventType::RoomAliases:
+ return qml_mtx_events::EventType::Aliases;
+ case EventType::RoomAvatar:
+ return qml_mtx_events::EventType::Avatar;
+ case EventType::RoomCanonicalAlias:
+ return qml_mtx_events::EventType::CanonicalAlias;
+ case EventType::RoomCreate:
+ return qml_mtx_events::EventType::RoomCreate;
+ case EventType::RoomEncrypted:
+ return qml_mtx_events::EventType::Encrypted;
+ case EventType::RoomEncryption:
+ return qml_mtx_events::EventType::Encryption;
+ case EventType::RoomGuestAccess:
+ return qml_mtx_events::EventType::RoomGuestAccess;
+ case EventType::RoomHistoryVisibility:
+ return qml_mtx_events::EventType::RoomHistoryVisibility;
+ case EventType::RoomJoinRules:
+ return qml_mtx_events::EventType::RoomJoinRules;
+ case EventType::RoomMember:
+ return qml_mtx_events::EventType::Member;
+ case EventType::RoomMessage:
+ return qml_mtx_events::EventType::UnknownMessage;
+ case EventType::RoomName:
+ return qml_mtx_events::EventType::Name;
+ case EventType::RoomPowerLevels:
+ return qml_mtx_events::EventType::PowerLevels;
+ case EventType::RoomTopic:
+ return qml_mtx_events::EventType::Topic;
+ case EventType::RoomTombstone:
+ return qml_mtx_events::EventType::Tombstone;
+ case EventType::RoomRedaction:
+ return qml_mtx_events::EventType::Redaction;
+ case EventType::RoomPinnedEvents:
+ return qml_mtx_events::EventType::PinnedEvents;
+ case EventType::Sticker:
+ return qml_mtx_events::EventType::Sticker;
+ case EventType::Tag:
+ return qml_mtx_events::EventType::Tag;
+ case EventType::Unsupported:
+ return qml_mtx_events::EventType::Unsupported;
+ default:
+ return qml_mtx_events::EventType::UnknownMessage;
+ }
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Audio> &)
+ {
+ return qml_mtx_events::EventType::AudioMessage;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &)
+ {
+ return qml_mtx_events::EventType::EmoteMessage;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::File> &)
+ {
+ return qml_mtx_events::EventType::FileMessage;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Image> &)
+ {
+ return qml_mtx_events::EventType::ImageMessage;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Notice> &)
+ {
+ return qml_mtx_events::EventType::NoticeMessage;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Text> &)
+ {
+ return qml_mtx_events::EventType::TextMessage;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Video> &)
+ {
+ return qml_mtx_events::EventType::VideoMessage;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationRequest> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationRequest;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationStart> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationStart;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationMac> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationMac;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationAccept> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationAccept;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationReady> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationReady;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationCancel> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationCancel;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationKey> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationKey;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::KeyVerificationDone> &)
+ {
+ return qml_mtx_events::EventType::KeyVerificationDone;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Redacted> &)
+ {
+ return qml_mtx_events::EventType::Redacted;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::CallInvite> &)
+ {
+ return qml_mtx_events::EventType::CallInvite;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::CallAnswer> &)
+ {
+ return qml_mtx_events::EventType::CallAnswer;
+ }
+ qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::CallHangUp> &)
+ {
+ return qml_mtx_events::EventType::CallHangUp;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallCandidates> &)
+ {
+ return qml_mtx_events::EventType::CallCandidates;
+ }
+ // ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return
+ // ::EventType::LocationMessage; }
};
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::collections::TimelineEvents &event)
{
- return std::visit(RoomEventType{}, event);
+ return std::visit(RoomEventType{}, event);
}
QString
toRoomEventTypeString(const mtx::events::collections::TimelineEvents &event)
{
- return std::visit([](const auto &e) { return QString::fromStdString(to_string(e.type)); },
- event);
+ return std::visit([](const auto &e) { return QString::fromStdString(to_string(e.type)); },
+ event);
}
mtx::events::EventType
qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
{
- switch (t) {
- // Unsupported event
- case qml_mtx_events::Unsupported:
- return mtx::events::EventType::Unsupported;
-
- /// m.room_key_request
- case qml_mtx_events::KeyRequest:
- return mtx::events::EventType::RoomKeyRequest;
- /// m.reaction:
- case qml_mtx_events::Reaction:
- return mtx::events::EventType::Reaction;
- /// m.room.aliases
- case qml_mtx_events::Aliases:
- return mtx::events::EventType::RoomAliases;
- /// m.room.avatar
- case qml_mtx_events::Avatar:
- return mtx::events::EventType::RoomAvatar;
- /// m.call.invite
- case qml_mtx_events::CallInvite:
- return mtx::events::EventType::CallInvite;
- /// m.call.answer
- case qml_mtx_events::CallAnswer:
- return mtx::events::EventType::CallAnswer;
- /// m.call.hangup
- case qml_mtx_events::CallHangUp:
- return mtx::events::EventType::CallHangUp;
- /// m.call.candidates
- case qml_mtx_events::CallCandidates:
- return mtx::events::EventType::CallCandidates;
- /// m.room.canonical_alias
- case qml_mtx_events::CanonicalAlias:
- return mtx::events::EventType::RoomCanonicalAlias;
- /// m.room.create
- case qml_mtx_events::RoomCreate:
- return mtx::events::EventType::RoomCreate;
- /// m.room.encrypted.
- case qml_mtx_events::Encrypted:
- return mtx::events::EventType::RoomEncrypted;
- /// m.room.encryption.
- case qml_mtx_events::Encryption:
- return mtx::events::EventType::RoomEncryption;
- /// m.room.guest_access
- case qml_mtx_events::RoomGuestAccess:
- return mtx::events::EventType::RoomGuestAccess;
- /// m.room.history_visibility
- case qml_mtx_events::RoomHistoryVisibility:
- return mtx::events::EventType::RoomHistoryVisibility;
- /// m.room.join_rules
- case qml_mtx_events::RoomJoinRules:
- return mtx::events::EventType::RoomJoinRules;
- /// m.room.member
- case qml_mtx_events::Member:
- return mtx::events::EventType::RoomMember;
- /// m.room.name
- case qml_mtx_events::Name:
- return mtx::events::EventType::RoomName;
- /// m.room.power_levels
- case qml_mtx_events::PowerLevels:
- return mtx::events::EventType::RoomPowerLevels;
- /// m.room.tombstone
- case qml_mtx_events::Tombstone:
- return mtx::events::EventType::RoomTombstone;
- /// m.room.topic
- case qml_mtx_events::Topic:
- return mtx::events::EventType::RoomTopic;
- /// m.room.redaction
- case qml_mtx_events::Redaction:
- return mtx::events::EventType::RoomRedaction;
- /// m.room.pinned_events
- case qml_mtx_events::PinnedEvents:
- return mtx::events::EventType::RoomPinnedEvents;
- // m.sticker
- case qml_mtx_events::Sticker:
- return mtx::events::EventType::Sticker;
- // m.tag
- case qml_mtx_events::Tag:
- return mtx::events::EventType::Tag;
- /// m.room.message
- case qml_mtx_events::AudioMessage:
- case qml_mtx_events::EmoteMessage:
- case qml_mtx_events::FileMessage:
- case qml_mtx_events::ImageMessage:
- case qml_mtx_events::LocationMessage:
- case qml_mtx_events::NoticeMessage:
- case qml_mtx_events::TextMessage:
- case qml_mtx_events::VideoMessage:
- case qml_mtx_events::Redacted:
- case qml_mtx_events::UnknownMessage:
- case qml_mtx_events::KeyVerificationRequest:
- case qml_mtx_events::KeyVerificationStart:
- case qml_mtx_events::KeyVerificationMac:
- case qml_mtx_events::KeyVerificationAccept:
- case qml_mtx_events::KeyVerificationCancel:
- case qml_mtx_events::KeyVerificationKey:
- case qml_mtx_events::KeyVerificationDone:
- case qml_mtx_events::KeyVerificationReady:
- return mtx::events::EventType::RoomMessage;
- //! m.image_pack, currently im.ponies.room_emotes
- case qml_mtx_events::ImagePackInRoom:
- return mtx::events::EventType::ImagePackInRoom;
- //! m.image_pack, currently im.ponies.user_emotes
- case qml_mtx_events::ImagePackInAccountData:
- return mtx::events::EventType::ImagePackInAccountData;
- //! m.image_pack.rooms, currently im.ponies.emote_rooms
- case qml_mtx_events::ImagePackRooms:
- return mtx::events::EventType::ImagePackRooms;
- default:
- return mtx::events::EventType::Unsupported;
- };
+ switch (t) {
+ // Unsupported event
+ case qml_mtx_events::Unsupported:
+ return mtx::events::EventType::Unsupported;
+
+ /// m.room_key_request
+ case qml_mtx_events::KeyRequest:
+ return mtx::events::EventType::RoomKeyRequest;
+ /// m.reaction:
+ case qml_mtx_events::Reaction:
+ return mtx::events::EventType::Reaction;
+ /// m.room.aliases
+ case qml_mtx_events::Aliases:
+ return mtx::events::EventType::RoomAliases;
+ /// m.room.avatar
+ case qml_mtx_events::Avatar:
+ return mtx::events::EventType::RoomAvatar;
+ /// m.call.invite
+ case qml_mtx_events::CallInvite:
+ return mtx::events::EventType::CallInvite;
+ /// m.call.answer
+ case qml_mtx_events::CallAnswer:
+ return mtx::events::EventType::CallAnswer;
+ /// m.call.hangup
+ case qml_mtx_events::CallHangUp:
+ return mtx::events::EventType::CallHangUp;
+ /// m.call.candidates
+ case qml_mtx_events::CallCandidates:
+ return mtx::events::EventType::CallCandidates;
+ /// m.room.canonical_alias
+ case qml_mtx_events::CanonicalAlias:
+ return mtx::events::EventType::RoomCanonicalAlias;
+ /// m.room.create
+ case qml_mtx_events::RoomCreate:
+ return mtx::events::EventType::RoomCreate;
+ /// m.room.encrypted.
+ case qml_mtx_events::Encrypted:
+ return mtx::events::EventType::RoomEncrypted;
+ /// m.room.encryption.
+ case qml_mtx_events::Encryption:
+ return mtx::events::EventType::RoomEncryption;
+ /// m.room.guest_access
+ case qml_mtx_events::RoomGuestAccess:
+ return mtx::events::EventType::RoomGuestAccess;
+ /// m.room.history_visibility
+ case qml_mtx_events::RoomHistoryVisibility:
+ return mtx::events::EventType::RoomHistoryVisibility;
+ /// m.room.join_rules
+ case qml_mtx_events::RoomJoinRules:
+ return mtx::events::EventType::RoomJoinRules;
+ /// m.room.member
+ case qml_mtx_events::Member:
+ return mtx::events::EventType::RoomMember;
+ /// m.room.name
+ case qml_mtx_events::Name:
+ return mtx::events::EventType::RoomName;
+ /// m.room.power_levels
+ case qml_mtx_events::PowerLevels:
+ return mtx::events::EventType::RoomPowerLevels;
+ /// m.room.tombstone
+ case qml_mtx_events::Tombstone:
+ return mtx::events::EventType::RoomTombstone;
+ /// m.room.topic
+ case qml_mtx_events::Topic:
+ return mtx::events::EventType::RoomTopic;
+ /// m.room.redaction
+ case qml_mtx_events::Redaction:
+ return mtx::events::EventType::RoomRedaction;
+ /// m.room.pinned_events
+ case qml_mtx_events::PinnedEvents:
+ return mtx::events::EventType::RoomPinnedEvents;
+ // m.sticker
+ case qml_mtx_events::Sticker:
+ return mtx::events::EventType::Sticker;
+ // m.tag
+ case qml_mtx_events::Tag:
+ return mtx::events::EventType::Tag;
+ /// m.room.message
+ case qml_mtx_events::AudioMessage:
+ case qml_mtx_events::EmoteMessage:
+ case qml_mtx_events::FileMessage:
+ case qml_mtx_events::ImageMessage:
+ case qml_mtx_events::LocationMessage:
+ case qml_mtx_events::NoticeMessage:
+ case qml_mtx_events::TextMessage:
+ case qml_mtx_events::VideoMessage:
+ case qml_mtx_events::Redacted:
+ case qml_mtx_events::UnknownMessage:
+ case qml_mtx_events::KeyVerificationRequest:
+ case qml_mtx_events::KeyVerificationStart:
+ case qml_mtx_events::KeyVerificationMac:
+ case qml_mtx_events::KeyVerificationAccept:
+ case qml_mtx_events::KeyVerificationCancel:
+ case qml_mtx_events::KeyVerificationKey:
+ case qml_mtx_events::KeyVerificationDone:
+ case qml_mtx_events::KeyVerificationReady:
+ return mtx::events::EventType::RoomMessage;
+ //! m.image_pack, currently im.ponies.room_emotes
+ case qml_mtx_events::ImagePackInRoom:
+ return mtx::events::EventType::ImagePackInRoom;
+ //! m.image_pack, currently im.ponies.user_emotes
+ case qml_mtx_events::ImagePackInAccountData:
+ return mtx::events::EventType::ImagePackInAccountData;
+ //! m.image_pack.rooms, currently im.ponies.emote_rooms
+ case qml_mtx_events::ImagePackRooms:
+ return mtx::events::EventType::ImagePackRooms;
+ default:
+ return mtx::events::EventType::Unsupported;
+ };
}
TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObject *parent)
@@ -329,566 +326,549 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
, manager_(manager)
, permissions_{room_id}
{
- lastMessage_.timestamp = 0;
-
- if (auto create =
- cache::client()->getStateEvent<mtx::events::state::Create>(room_id.toStdString()))
- this->isSpace_ = create->content.type == mtx::events::state::room_type::space;
- this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
-
- // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it
- // needs to be
- connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged);
-
- connect(
- this,
- &TimelineModel::redactionFailed,
- this,
- [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
- Qt::QueuedConnection);
-
- connect(this,
- &TimelineModel::newMessageToSend,
- this,
- &TimelineModel::addPendingMessage,
- Qt::QueuedConnection);
- connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
-
- connect(&events, &EventStore::dataChanged, this, [this](int from, int to) {
- relatedEventCacheBuster++;
- nhlog::ui()->debug(
- "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
- emit dataChanged(index(events.size() - to - 1, 0),
- index(events.size() - from - 1, 0));
- });
-
- connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
- int first = events.size() - to;
- int last = events.size() - from;
- if (from >= events.size()) {
- int batch_size = to - from;
- first += batch_size;
- last += batch_size;
- } else {
- first -= 1;
- last -= 1;
- }
- nhlog::ui()->debug("begin insert from {} to {}", first, last);
- beginInsertRows(QModelIndex(), first, last);
- });
- connect(&events, &EventStore::endInsertRows, this, [this]() { endInsertRows(); });
- connect(&events, &EventStore::beginResetModel, this, [this]() { beginResetModel(); });
- connect(&events, &EventStore::endResetModel, this, [this]() { endResetModel(); });
- connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage);
- connect(
- &events, &EventStore::fetchedMore, this, [this]() { setPaginationInProgress(false); });
- connect(&events,
- &EventStore::startDMVerification,
- this,
- [this](mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> msg) {
- ChatPage::instance()->receivedRoomDeviceVerificationRequest(msg, this);
- });
- connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
- this->updateFlowEventId(event_id);
- });
-
- // When a message is sent, check if the current edit/reply relates to that message,
- // and update the event_id so that it points to the sent message and not the pending one.
- connect(&events,
- &EventStore::messageSent,
- this,
- [this](std::string txn_id, std::string event_id) {
- if (edit_.toStdString() == txn_id) {
- edit_ = QString::fromStdString(event_id);
- emit editChanged(edit_);
- }
- if (reply_.toStdString() == txn_id) {
- reply_ = QString::fromStdString(event_id);
- emit replyChanged(reply_);
- }
- });
-
- connect(manager_,
- &TimelineViewManager::initialSyncChanged,
- &events,
- &EventStore::enableKeyRequests);
-
- connect(this, &TimelineModel::encryptionChanged, this, &TimelineModel::trustlevelChanged);
- connect(
- this, &TimelineModel::roomMemberCountChanged, this, &TimelineModel::trustlevelChanged);
- connect(cache::client(),
- &Cache::verificationStatusChanged,
- this,
- &TimelineModel::trustlevelChanged);
-
- showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
+ lastMessage_.timestamp = 0;
+
+ if (auto create =
+ cache::client()->getStateEvent<mtx::events::state::Create>(room_id.toStdString()))
+ this->isSpace_ = create->content.type == mtx::events::state::room_type::space;
+ this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
+
+ // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it
+ // needs to be
+ connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged);
+
+ connect(
+ this,
+ &TimelineModel::redactionFailed,
+ this,
+ [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
+ Qt::QueuedConnection);
+
+ connect(this,
+ &TimelineModel::newMessageToSend,
+ this,
+ &TimelineModel::addPendingMessage,
+ Qt::QueuedConnection);
+ connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
+
+ connect(&events, &EventStore::dataChanged, this, [this](int from, int to) {
+ relatedEventCacheBuster++;
+ nhlog::ui()->debug(
+ "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
+ emit dataChanged(index(events.size() - to - 1, 0), index(events.size() - from - 1, 0));
+ });
+
+ connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
+ int first = events.size() - to;
+ int last = events.size() - from;
+ if (from >= events.size()) {
+ int batch_size = to - from;
+ first += batch_size;
+ last += batch_size;
+ } else {
+ first -= 1;
+ last -= 1;
+ }
+ nhlog::ui()->debug("begin insert from {} to {}", first, last);
+ beginInsertRows(QModelIndex(), first, last);
+ });
+ connect(&events, &EventStore::endInsertRows, this, [this]() { endInsertRows(); });
+ connect(&events, &EventStore::beginResetModel, this, [this]() { beginResetModel(); });
+ connect(&events, &EventStore::endResetModel, this, [this]() { endResetModel(); });
+ connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage);
+ connect(&events, &EventStore::fetchedMore, this, [this]() { setPaginationInProgress(false); });
+ connect(&events,
+ &EventStore::startDMVerification,
+ this,
+ [this](mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> msg) {
+ ChatPage::instance()->receivedRoomDeviceVerificationRequest(msg, this);
+ });
+ connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
+ this->updateFlowEventId(event_id);
+ });
+
+ // When a message is sent, check if the current edit/reply relates to that message,
+ // and update the event_id so that it points to the sent message and not the pending one.
+ connect(
+ &events, &EventStore::messageSent, this, [this](std::string txn_id, std::string event_id) {
+ if (edit_.toStdString() == txn_id) {
+ edit_ = QString::fromStdString(event_id);
+ emit editChanged(edit_);
+ }
+ if (reply_.toStdString() == txn_id) {
+ reply_ = QString::fromStdString(event_id);
+ emit replyChanged(reply_);
+ }
+ });
+
+ connect(
+ manager_, &TimelineViewManager::initialSyncChanged, &events, &EventStore::enableKeyRequests);
+
+ connect(this, &TimelineModel::encryptionChanged, this, &TimelineModel::trustlevelChanged);
+ connect(this, &TimelineModel::roomMemberCountChanged, this, &TimelineModel::trustlevelChanged);
+ connect(
+ cache::client(), &Cache::verificationStatusChanged, this, &TimelineModel::trustlevelChanged);
+
+ showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
}
QHash<int, QByteArray>
TimelineModel::roleNames() const
{
- return {
- {Type, "type"},
- {TypeString, "typeString"},
- {IsOnlyEmoji, "isOnlyEmoji"},
- {Body, "body"},
- {FormattedBody, "formattedBody"},
- {PreviousMessageUserId, "previousMessageUserId"},
- {IsSender, "isSender"},
- {UserId, "userId"},
- {UserName, "userName"},
- {PreviousMessageDay, "previousMessageDay"},
- {Day, "day"},
- {Timestamp, "timestamp"},
- {Url, "url"},
- {ThumbnailUrl, "thumbnailUrl"},
- {Blurhash, "blurhash"},
- {Filename, "filename"},
- {Filesize, "filesize"},
- {MimeType, "mimetype"},
- {OriginalHeight, "originalHeight"},
- {OriginalWidth, "originalWidth"},
- {ProportionalHeight, "proportionalHeight"},
- {EventId, "eventId"},
- {State, "status"},
- {IsEdited, "isEdited"},
- {IsEditable, "isEditable"},
- {IsEncrypted, "isEncrypted"},
- {Trustlevel, "trustlevel"},
- {EncryptionError, "encryptionError"},
- {ReplyTo, "replyTo"},
- {Reactions, "reactions"},
- {RoomId, "roomId"},
- {RoomName, "roomName"},
- {RoomTopic, "roomTopic"},
- {CallType, "callType"},
- {Dump, "dump"},
- {RelatedEventCacheBuster, "relatedEventCacheBuster"},
- };
+ return {
+ {Type, "type"},
+ {TypeString, "typeString"},
+ {IsOnlyEmoji, "isOnlyEmoji"},
+ {Body, "body"},
+ {FormattedBody, "formattedBody"},
+ {PreviousMessageUserId, "previousMessageUserId"},
+ {IsSender, "isSender"},
+ {UserId, "userId"},
+ {UserName, "userName"},
+ {PreviousMessageDay, "previousMessageDay"},
+ {Day, "day"},
+ {Timestamp, "timestamp"},
+ {Url, "url"},
+ {ThumbnailUrl, "thumbnailUrl"},
+ {Blurhash, "blurhash"},
+ {Filename, "filename"},
+ {Filesize, "filesize"},
+ {MimeType, "mimetype"},
+ {OriginalHeight, "originalHeight"},
+ {OriginalWidth, "originalWidth"},
+ {ProportionalHeight, "proportionalHeight"},
+ {EventId, "eventId"},
+ {State, "status"},
+ {IsEdited, "isEdited"},
+ {IsEditable, "isEditable"},
+ {IsEncrypted, "isEncrypted"},
+ {Trustlevel, "trustlevel"},
+ {EncryptionError, "encryptionError"},
+ {ReplyTo, "replyTo"},
+ {Reactions, "reactions"},
+ {RoomId, "roomId"},
+ {RoomName, "roomName"},
+ {RoomTopic, "roomTopic"},
+ {CallType, "callType"},
+ {Dump, "dump"},
+ {RelatedEventCacheBuster, "relatedEventCacheBuster"},
+ };
}
int
TimelineModel::rowCount(const QModelIndex &parent) const
{
- Q_UNUSED(parent);
- return this->events.size();
+ Q_UNUSED(parent);
+ return this->events.size();
}
QVariantMap
TimelineModel::getDump(QString eventId, QString relatedTo) const
{
- if (auto event = events.get(eventId.toStdString(), relatedTo.toStdString()))
- return data(*event, Dump).toMap();
- return {};
+ if (auto event = events.get(eventId.toStdString(), relatedTo.toStdString()))
+ return data(*event, Dump).toMap();
+ return {};
}
QVariant
TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int role) const
{
- using namespace mtx::accessors;
- namespace acc = mtx::accessors;
-
- switch (role) {
- case IsSender:
- return QVariant(acc::sender(event) == http::client()->user_id().to_string());
- case UserId:
- return QVariant(QString::fromStdString(acc::sender(event)));
- case UserName:
- return QVariant(displayName(QString::fromStdString(acc::sender(event))));
-
- case Day: {
- QDateTime prevDate = origin_server_ts(event);
- prevDate.setTime(QTime());
- return QVariant(prevDate.toMSecsSinceEpoch());
- }
- case Timestamp:
- return QVariant(origin_server_ts(event));
- case Type:
- return QVariant(toRoomEventType(event));
- case TypeString:
- return QVariant(toRoomEventTypeString(event));
- case IsOnlyEmoji: {
- QString qBody = QString::fromStdString(body(event));
-
- QVector<uint> utf32_string = qBody.toUcs4();
- int emojiCount = 0;
-
- for (auto &code : utf32_string) {
- if (utils::codepointIsEmoji(code)) {
- emojiCount++;
- } else {
- return QVariant(0);
- }
- }
-
- return QVariant(emojiCount);
- }
- case Body:
- return QVariant(
- utils::replaceEmoji(QString::fromStdString(body(event)).toHtmlEscaped()));
- case FormattedBody: {
- const static QRegularExpression replyFallback(
- "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
-
- auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent();
-
- bool isReply = utils::isReply(event);
-
- auto formattedBody_ = QString::fromStdString(formatted_body(event));
- if (formattedBody_.isEmpty()) {
- auto body_ = QString::fromStdString(body(event));
-
- if (isReply) {
- while (body_.startsWith("> "))
- body_ = body_.right(body_.size() - body_.indexOf('\n') - 1);
- if (body_.startsWith('\n'))
- body_ = body_.right(body_.size() - 1);
- }
- formattedBody_ = body_.toHtmlEscaped().replace('\n', "<br>");
- } else {
- if (isReply)
- formattedBody_ = formattedBody_.remove(replyFallback);
- }
-
- // TODO(Nico): Don't parse html with a regex
- const static QRegularExpression matchImgUri(
- "(<img [^>]*)src=\"mxc://([^\"]*)\"([^>]*>)");
- formattedBody_.replace(matchImgUri, "\\1 src=\"image://mxcImage/\\2\"\\3");
- // Same regex but for single quotes around the src
- const static QRegularExpression matchImgUri2(
- "(<img [^>]*)src=\'mxc://([^\']*)\'([^>]*>)");
- formattedBody_.replace(matchImgUri2, "\\1 src=\"image://mxcImage/\\2\"\\3");
- const static QRegularExpression matchEmoticonHeight(
- "(<img data-mx-emoticon [^>]*)height=\"([^\"]*)\"([^>]*>)");
- formattedBody_.replace(matchEmoticonHeight,
- QString("\\1 height=\"%1\"\\3").arg(ascent));
-
- return QVariant(utils::replaceEmoji(
- utils::linkifyMessage(utils::escapeBlacklistedHtml(formattedBody_))));
- }
- case Url:
- return QVariant(QString::fromStdString(url(event)));
- case ThumbnailUrl:
- return QVariant(QString::fromStdString(thumbnail_url(event)));
- case Blurhash:
- return QVariant(QString::fromStdString(blurhash(event)));
- case Filename:
- return QVariant(QString::fromStdString(filename(event)));
- case Filesize:
- return QVariant(utils::humanReadableFileSize(filesize(event)));
- case MimeType:
- return QVariant(QString::fromStdString(mimetype(event)));
- case OriginalHeight:
- return QVariant(qulonglong{media_height(event)});
- case OriginalWidth:
- return QVariant(qulonglong{media_width(event)});
- case ProportionalHeight: {
- auto w = media_width(event);
- if (w == 0)
- w = 1;
-
- double prop = media_height(event) / (double)w;
-
- return QVariant(prop > 0 ? prop : 1.);
- }
- case EventId: {
- if (auto replaces = relations(event).replaces())
- return QVariant(QString::fromStdString(replaces.value()));
- else
- return QVariant(QString::fromStdString(event_id(event)));
- }
- case State: {
- auto id = QString::fromStdString(event_id(event));
- auto containsOthers = [](const auto &vec) {
- for (const auto &e : vec)
- if (e.second != http::client()->user_id().to_string())
- return true;
- return false;
- };
-
- // only show read receipts for messages not from us
- if (acc::sender(event) != http::client()->user_id().to_string())
- return qml_mtx_events::Empty;
- else if (!id.isEmpty() && id[0] == "m")
- return qml_mtx_events::Sent;
- else if (read.contains(id) || containsOthers(cache::readReceipts(id, room_id_)))
- return qml_mtx_events::Read;
- else
- return qml_mtx_events::Received;
- }
- case IsEdited:
- return QVariant(relations(event).replaces().has_value());
- case IsEditable:
- return QVariant(!is_state_event(event) && mtx::accessors::sender(event) ==
- http::client()->user_id().to_string());
- case IsEncrypted: {
- auto id = event_id(event);
- auto encrypted_event = events.get(id, "", false);
- return encrypted_event &&
- std::holds_alternative<
- mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- *encrypted_event);
- }
-
- case Trustlevel: {
- auto id = event_id(event);
- auto encrypted_event = events.get(id, "", false);
- if (encrypted_event) {
- if (auto encrypted =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- &*encrypted_event)) {
- return olm::calculate_trust(
- encrypted->sender,
- MegolmSessionIndex(room_id_.toStdString(), encrypted->content));
- }
- }
- return crypto::Trust::Unverified;
- }
-
- case EncryptionError:
- return events.decryptionError(event_id(event));
+ using namespace mtx::accessors;
+ namespace acc = mtx::accessors;
+
+ switch (role) {
+ case IsSender:
+ return QVariant(acc::sender(event) == http::client()->user_id().to_string());
+ case UserId:
+ return QVariant(QString::fromStdString(acc::sender(event)));
+ case UserName:
+ return QVariant(displayName(QString::fromStdString(acc::sender(event))));
+
+ case Day: {
+ QDateTime prevDate = origin_server_ts(event);
+ prevDate.setTime(QTime());
+ return QVariant(prevDate.toMSecsSinceEpoch());
+ }
+ case Timestamp:
+ return QVariant(origin_server_ts(event));
+ case Type:
+ return QVariant(toRoomEventType(event));
+ case TypeString:
+ return QVariant(toRoomEventTypeString(event));
+ case IsOnlyEmoji: {
+ QString qBody = QString::fromStdString(body(event));
+
+ QVector<uint> utf32_string = qBody.toUcs4();
+ int emojiCount = 0;
+
+ for (auto &code : utf32_string) {
+ if (utils::codepointIsEmoji(code)) {
+ emojiCount++;
+ } else {
+ return QVariant(0);
+ }
+ }
+
+ return QVariant(emojiCount);
+ }
+ case Body:
+ return QVariant(utils::replaceEmoji(QString::fromStdString(body(event)).toHtmlEscaped()));
+ case FormattedBody: {
+ const static QRegularExpression replyFallback(
+ "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
+
+ auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent();
+
+ bool isReply = utils::isReply(event);
+
+ auto formattedBody_ = QString::fromStdString(formatted_body(event));
+ if (formattedBody_.isEmpty()) {
+ auto body_ = QString::fromStdString(body(event));
+
+ if (isReply) {
+ while (body_.startsWith("> "))
+ body_ = body_.right(body_.size() - body_.indexOf('\n') - 1);
+ if (body_.startsWith('\n'))
+ body_ = body_.right(body_.size() - 1);
+ }
+ formattedBody_ = body_.toHtmlEscaped().replace('\n', "<br>");
+ } else {
+ if (isReply)
+ formattedBody_ = formattedBody_.remove(replyFallback);
+ }
+
+ // TODO(Nico): Don't parse html with a regex
+ const static QRegularExpression matchImgUri("(<img [^>]*)src=\"mxc://([^\"]*)\"([^>]*>)");
+ formattedBody_.replace(matchImgUri, "\\1 src=\"image://mxcImage/\\2\"\\3");
+ // Same regex but for single quotes around the src
+ const static QRegularExpression matchImgUri2("(<img [^>]*)src=\'mxc://([^\']*)\'([^>]*>)");
+ formattedBody_.replace(matchImgUri2, "\\1 src=\"image://mxcImage/\\2\"\\3");
+ const static QRegularExpression matchEmoticonHeight(
+ "(<img data-mx-emoticon [^>]*)height=\"([^\"]*)\"([^>]*>)");
+ formattedBody_.replace(matchEmoticonHeight, QString("\\1 height=\"%1\"\\3").arg(ascent));
+
+ return QVariant(
+ utils::replaceEmoji(utils::linkifyMessage(utils::escapeBlacklistedHtml(formattedBody_))));
+ }
+ case Url:
+ return QVariant(QString::fromStdString(url(event)));
+ case ThumbnailUrl:
+ return QVariant(QString::fromStdString(thumbnail_url(event)));
+ case Blurhash:
+ return QVariant(QString::fromStdString(blurhash(event)));
+ case Filename:
+ return QVariant(QString::fromStdString(filename(event)));
+ case Filesize:
+ return QVariant(utils::humanReadableFileSize(filesize(event)));
+ case MimeType:
+ return QVariant(QString::fromStdString(mimetype(event)));
+ case OriginalHeight:
+ return QVariant(qulonglong{media_height(event)});
+ case OriginalWidth:
+ return QVariant(qulonglong{media_width(event)});
+ case ProportionalHeight: {
+ auto w = media_width(event);
+ if (w == 0)
+ w = 1;
+
+ double prop = media_height(event) / (double)w;
+
+ return QVariant(prop > 0 ? prop : 1.);
+ }
+ case EventId: {
+ if (auto replaces = relations(event).replaces())
+ return QVariant(QString::fromStdString(replaces.value()));
+ else
+ return QVariant(QString::fromStdString(event_id(event)));
+ }
+ case State: {
+ auto id = QString::fromStdString(event_id(event));
+ auto containsOthers = [](const auto &vec) {
+ for (const auto &e : vec)
+ if (e.second != http::client()->user_id().to_string())
+ return true;
+ return false;
+ };
- case ReplyTo:
- return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
- case Reactions: {
- auto id = relations(event).replaces().value_or(event_id(event));
- return QVariant::fromValue(events.reactions(id));
- }
- case RoomId:
- return QVariant(room_id_);
- case RoomName:
- return QVariant(
- utils::replaceEmoji(QString::fromStdString(room_name(event)).toHtmlEscaped()));
- case RoomTopic:
- return QVariant(utils::replaceEmoji(
- utils::linkifyMessage(QString::fromStdString(room_topic(event))
- .toHtmlEscaped()
- .replace("\n", "<br>"))));
- case CallType:
- return QVariant(QString::fromStdString(call_type(event)));
- case Dump: {
- QVariantMap m;
- auto names = roleNames();
-
- m.insert(names[Type], data(event, static_cast<int>(Type)));
- m.insert(names[TypeString], data(event, static_cast<int>(TypeString)));
- m.insert(names[IsOnlyEmoji], data(event, static_cast<int>(IsOnlyEmoji)));
- m.insert(names[Body], data(event, static_cast<int>(Body)));
- m.insert(names[FormattedBody], data(event, static_cast<int>(FormattedBody)));
- m.insert(names[IsSender], data(event, static_cast<int>(IsSender)));
- m.insert(names[UserId], data(event, static_cast<int>(UserId)));
- m.insert(names[UserName], data(event, static_cast<int>(UserName)));
- m.insert(names[Day], data(event, static_cast<int>(Day)));
- m.insert(names[Timestamp], data(event, static_cast<int>(Timestamp)));
- m.insert(names[Url], data(event, static_cast<int>(Url)));
- m.insert(names[ThumbnailUrl], data(event, static_cast<int>(ThumbnailUrl)));
- m.insert(names[Blurhash], data(event, static_cast<int>(Blurhash)));
- m.insert(names[Filename], data(event, static_cast<int>(Filename)));
- m.insert(names[Filesize], data(event, static_cast<int>(Filesize)));
- m.insert(names[MimeType], data(event, static_cast<int>(MimeType)));
- m.insert(names[OriginalHeight], data(event, static_cast<int>(OriginalHeight)));
- m.insert(names[OriginalWidth], data(event, static_cast<int>(OriginalWidth)));
- m.insert(names[ProportionalHeight],
- data(event, static_cast<int>(ProportionalHeight)));
- m.insert(names[EventId], data(event, static_cast<int>(EventId)));
- m.insert(names[State], data(event, static_cast<int>(State)));
- m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited)));
- m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable)));
- m.insert(names[IsEncrypted], data(event, static_cast<int>(IsEncrypted)));
- m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo)));
- m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
- m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
- m.insert(names[CallType], data(event, static_cast<int>(CallType)));
- m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError)));
-
- return QVariant(m);
- }
- case RelatedEventCacheBuster:
- return relatedEventCacheBuster;
- default:
- return QVariant();
- }
+ // only show read receipts for messages not from us
+ if (acc::sender(event) != http::client()->user_id().to_string())
+ return qml_mtx_events::Empty;
+ else if (!id.isEmpty() && id[0] == "m")
+ return qml_mtx_events::Sent;
+ else if (read.contains(id) || containsOthers(cache::readReceipts(id, room_id_)))
+ return qml_mtx_events::Read;
+ else
+ return qml_mtx_events::Received;
+ }
+ case IsEdited:
+ return QVariant(relations(event).replaces().has_value());
+ case IsEditable:
+ return QVariant(!is_state_event(event) &&
+ mtx::accessors::sender(event) == http::client()->user_id().to_string());
+ case IsEncrypted: {
+ auto id = event_id(event);
+ auto encrypted_event = events.get(id, "", false);
+ return encrypted_event &&
+ std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ *encrypted_event);
+ }
+
+ case Trustlevel: {
+ auto id = event_id(event);
+ auto encrypted_event = events.get(id, "", false);
+ if (encrypted_event) {
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ &*encrypted_event)) {
+ return olm::calculate_trust(
+ encrypted->sender,
+ MegolmSessionIndex(room_id_.toStdString(), encrypted->content));
+ }
+ }
+ return crypto::Trust::Unverified;
+ }
+
+ case EncryptionError:
+ return events.decryptionError(event_id(event));
+
+ case ReplyTo:
+ return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
+ case Reactions: {
+ auto id = relations(event).replaces().value_or(event_id(event));
+ return QVariant::fromValue(events.reactions(id));
+ }
+ case RoomId:
+ return QVariant(room_id_);
+ case RoomName:
+ return QVariant(
+ utils::replaceEmoji(QString::fromStdString(room_name(event)).toHtmlEscaped()));
+ case RoomTopic:
+ return QVariant(utils::replaceEmoji(utils::linkifyMessage(
+ QString::fromStdString(room_topic(event)).toHtmlEscaped().replace("\n", "<br>"))));
+ case CallType:
+ return QVariant(QString::fromStdString(call_type(event)));
+ case Dump: {
+ QVariantMap m;
+ auto names = roleNames();
+
+ m.insert(names[Type], data(event, static_cast<int>(Type)));
+ m.insert(names[TypeString], data(event, static_cast<int>(TypeString)));
+ m.insert(names[IsOnlyEmoji], data(event, static_cast<int>(IsOnlyEmoji)));
+ m.insert(names[Body], data(event, static_cast<int>(Body)));
+ m.insert(names[FormattedBody], data(event, static_cast<int>(FormattedBody)));
+ m.insert(names[IsSender], data(event, static_cast<int>(IsSender)));
+ m.insert(names[UserId], data(event, static_cast<int>(UserId)));
+ m.insert(names[UserName], data(event, static_cast<int>(UserName)));
+ m.insert(names[Day], data(event, static_cast<int>(Day)));
+ m.insert(names[Timestamp], data(event, static_cast<int>(Timestamp)));
+ m.insert(names[Url], data(event, static_cast<int>(Url)));
+ m.insert(names[ThumbnailUrl], data(event, static_cast<int>(ThumbnailUrl)));
+ m.insert(names[Blurhash], data(event, static_cast<int>(Blurhash)));
+ m.insert(names[Filename], data(event, static_cast<int>(Filename)));
+ m.insert(names[Filesize], data(event, static_cast<int>(Filesize)));
+ m.insert(names[MimeType], data(event, static_cast<int>(MimeType)));
+ m.insert(names[OriginalHeight], data(event, static_cast<int>(OriginalHeight)));
+ m.insert(names[OriginalWidth], data(event, static_cast<int>(OriginalWidth)));
+ m.insert(names[ProportionalHeight], data(event, static_cast<int>(ProportionalHeight)));
+ m.insert(names[EventId], data(event, static_cast<int>(EventId)));
+ m.insert(names[State], data(event, static_cast<int>(State)));
+ m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited)));
+ m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable)));
+ m.insert(names[IsEncrypted], data(event, static_cast<int>(IsEncrypted)));
+ m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo)));
+ m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
+ m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
+ m.insert(names[CallType], data(event, static_cast<int>(CallType)));
+ m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError)));
+
+ return QVariant(m);
+ }
+ case RelatedEventCacheBuster:
+ return relatedEventCacheBuster;
+ default:
+ return QVariant();
+ }
}
QVariant
TimelineModel::data(const QModelIndex &index, int role) const
{
- using namespace mtx::accessors;
- namespace acc = mtx::accessors;
- if (index.row() < 0 && index.row() >= rowCount())
- return QVariant();
+ using namespace mtx::accessors;
+ namespace acc = mtx::accessors;
+ if (index.row() < 0 && index.row() >= rowCount())
+ return QVariant();
- // HACK(Nico): fetchMore likes to break with dynamically sized delegates and reuseItems
- if (index.row() + 1 == rowCount() && !m_paginationInProgress)
- const_cast<TimelineModel *>(this)->fetchMore(index);
+ // HACK(Nico): fetchMore likes to break with dynamically sized delegates and reuseItems
+ if (index.row() + 1 == rowCount() && !m_paginationInProgress)
+ const_cast<TimelineModel *>(this)->fetchMore(index);
- auto event = events.get(rowCount() - index.row() - 1);
+ auto event = events.get(rowCount() - index.row() - 1);
- if (!event)
- return "";
-
- if (role == PreviousMessageDay || role == PreviousMessageUserId) {
- int prevIdx = rowCount() - index.row() - 2;
- if (prevIdx < 0)
- return QVariant();
- auto tempEv = events.get(prevIdx);
- if (!tempEv)
- return QVariant();
- if (role == PreviousMessageUserId)
- return data(*tempEv, UserId);
- else
- return data(*tempEv, Day);
- }
+ if (!event)
+ return "";
- return data(*event, role);
+ if (role == PreviousMessageDay || role == PreviousMessageUserId) {
+ int prevIdx = rowCount() - index.row() - 2;
+ if (prevIdx < 0)
+ return QVariant();
+ auto tempEv = events.get(prevIdx);
+ if (!tempEv)
+ return QVariant();
+ if (role == PreviousMessageUserId)
+ return data(*tempEv, UserId);
+ else
+ return data(*tempEv, Day);
+ }
+
+ return data(*event, role);
}
QVariant
TimelineModel::dataById(QString id, int role, QString relatedTo)
{
- if (auto event = events.get(id.toStdString(), relatedTo.toStdString()))
- return data(*event, role);
- return QVariant();
+ if (auto event = events.get(id.toStdString(), relatedTo.toStdString()))
+ return data(*event, role);
+ return QVariant();
}
bool
TimelineModel::canFetchMore(const QModelIndex &) const
{
- if (!events.size())
- return true;
- if (auto first = events.get(0);
- first &&
- !std::holds_alternative<mtx::events::StateEvent<mtx::events::state::Create>>(*first))
- return true;
- else
+ if (!events.size())
+ return true;
+ if (auto first = events.get(0);
+ first &&
+ !std::holds_alternative<mtx::events::StateEvent<mtx::events::state::Create>>(*first))
+ return true;
+ else
- return false;
+ return false;
}
void
TimelineModel::setPaginationInProgress(const bool paginationInProgress)
{
- if (m_paginationInProgress == paginationInProgress) {
- return;
- }
+ if (m_paginationInProgress == paginationInProgress) {
+ return;
+ }
- m_paginationInProgress = paginationInProgress;
- emit paginationInProgressChanged(m_paginationInProgress);
+ m_paginationInProgress = paginationInProgress;
+ emit paginationInProgressChanged(m_paginationInProgress);
}
void
TimelineModel::fetchMore(const QModelIndex &)
{
- if (m_paginationInProgress) {
- nhlog::ui()->warn("Already loading older messages");
- return;
- }
+ if (m_paginationInProgress) {
+ nhlog::ui()->warn("Already loading older messages");
+ return;
+ }
- setPaginationInProgress(true);
+ setPaginationInProgress(true);
- events.fetchMore();
+ events.fetchMore();
}
void
TimelineModel::sync(const mtx::responses::JoinedRoom &room)
{
- this->syncState(room.state);
- this->addEvents(room.timeline);
+ this->syncState(room.state);
+ this->addEvents(room.timeline);
- if (room.unread_notifications.highlight_count != highlight_count ||
- room.unread_notifications.notification_count != notification_count) {
- notification_count = room.unread_notifications.notification_count;
- highlight_count = room.unread_notifications.highlight_count;
- emit notificationsChanged();
- }
+ if (room.unread_notifications.highlight_count != highlight_count ||
+ room.unread_notifications.notification_count != notification_count) {
+ notification_count = room.unread_notifications.notification_count;
+ highlight_count = room.unread_notifications.highlight_count;
+ emit notificationsChanged();
+ }
}
void
TimelineModel::syncState(const mtx::responses::State &s)
{
- using namespace mtx::events;
-
- for (const auto &e : s.events) {
- if (std::holds_alternative<StateEvent<state::Avatar>>(e))
- emit roomAvatarUrlChanged();
- else if (std::holds_alternative<StateEvent<state::Name>>(e))
- emit roomNameChanged();
- else if (std::holds_alternative<StateEvent<state::Topic>>(e))
- emit roomTopicChanged();
- else if (std::holds_alternative<StateEvent<state::Topic>>(e)) {
- permissions_.invalidate();
- emit permissionsChanged();
- } else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
- emit roomAvatarUrlChanged();
- emit roomNameChanged();
- emit roomMemberCountChanged();
-
- if (roomMemberCount() <= 2) {
- emit isDirectChanged();
- emit directChatOtherUserIdChanged();
- }
- } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) {
- this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
- emit encryptionChanged();
- }
- }
+ using namespace mtx::events;
+
+ for (const auto &e : s.events) {
+ if (std::holds_alternative<StateEvent<state::Avatar>>(e))
+ emit roomAvatarUrlChanged();
+ else if (std::holds_alternative<StateEvent<state::Name>>(e))
+ emit roomNameChanged();
+ else if (std::holds_alternative<StateEvent<state::Topic>>(e))
+ emit roomTopicChanged();
+ else if (std::holds_alternative<StateEvent<state::Topic>>(e)) {
+ permissions_.invalidate();
+ emit permissionsChanged();
+ } else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
+ emit roomAvatarUrlChanged();
+ emit roomNameChanged();
+ emit roomMemberCountChanged();
+
+ if (roomMemberCount() <= 2) {
+ emit isDirectChanged();
+ emit directChatOtherUserIdChanged();
+ }
+ } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) {
+ this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
+ emit encryptionChanged();
+ }
+ }
}
void
TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
{
- if (timeline.events.empty())
- return;
-
- events.handleSync(timeline);
-
- using namespace mtx::events;
-
- for (auto e : timeline.events) {
- if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) {
- MegolmSessionIndex index(room_id_.toStdString(), encryptedEvent->content);
-
- auto result = olm::decryptEvent(index, *encryptedEvent);
- if (result.event)
- e = result.event.value();
- }
+ if (timeline.events.empty())
+ return;
- if (std::holds_alternative<RoomEvent<msg::CallCandidates>>(e) ||
- std::holds_alternative<RoomEvent<msg::CallInvite>>(e) ||
- std::holds_alternative<RoomEvent<msg::CallAnswer>>(e) ||
- std::holds_alternative<RoomEvent<msg::CallHangUp>>(e))
- std::visit(
- [this](auto &event) {
- event.room_id = room_id_.toStdString();
- if constexpr (std::is_same_v<std::decay_t<decltype(event)>,
- RoomEvent<msg::CallAnswer>> ||
- std::is_same_v<std::decay_t<decltype(event)>,
- RoomEvent<msg::CallHangUp>>)
- emit newCallEvent(event);
- else {
- if (event.sender != http::client()->user_id().to_string())
- emit newCallEvent(event);
- }
- },
- e);
- else if (std::holds_alternative<StateEvent<state::Avatar>>(e))
- emit roomAvatarUrlChanged();
- else if (std::holds_alternative<StateEvent<state::Name>>(e))
- emit roomNameChanged();
- else if (std::holds_alternative<StateEvent<state::Topic>>(e))
- emit roomTopicChanged();
- else if (std::holds_alternative<StateEvent<state::PowerLevels>>(e)) {
- permissions_.invalidate();
- emit permissionsChanged();
- } else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
- emit roomAvatarUrlChanged();
- emit roomNameChanged();
- emit roomMemberCountChanged();
- } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) {
- this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
- emit encryptionChanged();
- }
- }
- updateLastMessage();
+ events.handleSync(timeline);
+
+ using namespace mtx::events;
+
+ for (auto e : timeline.events) {
+ if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) {
+ MegolmSessionIndex index(room_id_.toStdString(), encryptedEvent->content);
+
+ auto result = olm::decryptEvent(index, *encryptedEvent);
+ if (result.event)
+ e = result.event.value();
+ }
+
+ if (std::holds_alternative<RoomEvent<msg::CallCandidates>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallInvite>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallAnswer>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallHangUp>>(e))
+ std::visit(
+ [this](auto &event) {
+ event.room_id = room_id_.toStdString();
+ if constexpr (std::is_same_v<std::decay_t<decltype(event)>,
+ RoomEvent<msg::CallAnswer>> ||
+ std::is_same_v<std::decay_t<decltype(event)>,
+ RoomEvent<msg::CallHangUp>>)
+ emit newCallEvent(event);
+ else {
+ if (event.sender != http::client()->user_id().to_string())
+ emit newCallEvent(event);
+ }
+ },
+ e);
+ else if (std::holds_alternative<StateEvent<state::Avatar>>(e))
+ emit roomAvatarUrlChanged();
+ else if (std::holds_alternative<StateEvent<state::Name>>(e))
+ emit roomNameChanged();
+ else if (std::holds_alternative<StateEvent<state::Topic>>(e))
+ emit roomTopicChanged();
+ else if (std::holds_alternative<StateEvent<state::PowerLevels>>(e)) {
+ permissions_.invalidate();
+ emit permissionsChanged();
+ } else if (std::holds_alternative<StateEvent<state::Member>>(e)) {
+ emit roomAvatarUrlChanged();
+ emit roomNameChanged();
+ emit roomMemberCountChanged();
+ } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) {
+ this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
+ emit encryptionChanged();
+ }
+ }
+ updateLastMessage();
}
template<typename T>
@@ -896,1216 +876,1191 @@ auto
isMessage(const mtx::events::RoomEvent<T> &e)
-> std::enable_if_t<std::is_same<decltype(e.content.msgtype), std::string>::value, bool>
{
- return true;
+ return true;
}
template<typename T>
auto
isMessage(const mtx::events::Event<T> &)
{
- return false;
+ return false;
}
template<typename T>
auto
isMessage(const mtx::events::EncryptedEvent<T> &)
{
- return true;
+ return true;
}
auto
isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &)
{
- return true;
+ return true;
}
auto
isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &)
{
- return true;
+ return true;
}
auto
isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &)
{
- return true;
+ return true;
}
// Workaround. We also want to see a room at the top, if we just joined it
auto
isYourJoin(const mtx::events::StateEvent<mtx::events::state::Member> &e)
{
- return e.content.membership == mtx::events::state::Membership::Join &&
- e.state_key == http::client()->user_id().to_string();
+ return e.content.membership == mtx::events::state::Membership::Join &&
+ e.state_key == http::client()->user_id().to_string();
}
template<typename T>
auto
isYourJoin(const mtx::events::Event<T> &)
{
- return false;
+ return false;
}
void
TimelineModel::updateLastMessage()
{
- for (auto it = events.size() - 1; it >= 0; --it) {
- auto event = events.get(it, decryptDescription);
- if (!event)
- continue;
-
- if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, *event)) {
- auto time = mtx::accessors::origin_server_ts(*event);
- uint64_t ts = time.toMSecsSinceEpoch();
- auto description =
- DescInfo{QString::fromStdString(mtx::accessors::event_id(*event)),
- QString::fromStdString(http::client()->user_id().to_string()),
- tr("You joined this room."),
- utils::descriptiveTime(time),
- ts,
- time};
- if (description != lastMessage_) {
- lastMessage_ = description;
- emit lastMessageChanged();
- }
- return;
- }
- if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, *event))
- continue;
-
- auto description = utils::getMessageDescription(
- *event,
- QString::fromStdString(http::client()->user_id().to_string()),
- cache::displayName(room_id_,
- QString::fromStdString(mtx::accessors::sender(*event))));
- if (description != lastMessage_) {
- lastMessage_ = description;
- emit lastMessageChanged();
- }
- return;
+ for (auto it = events.size() - 1; it >= 0; --it) {
+ auto event = events.get(it, decryptDescription);
+ if (!event)
+ continue;
+
+ if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, *event)) {
+ auto time = mtx::accessors::origin_server_ts(*event);
+ uint64_t ts = time.toMSecsSinceEpoch();
+ auto description =
+ DescInfo{QString::fromStdString(mtx::accessors::event_id(*event)),
+ QString::fromStdString(http::client()->user_id().to_string()),
+ tr("You joined this room."),
+ utils::descriptiveTime(time),
+ ts,
+ time};
+ if (description != lastMessage_) {
+ lastMessage_ = description;
+ emit lastMessageChanged();
+ }
+ return;
+ }
+ if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, *event))
+ continue;
+
+ auto description = utils::getMessageDescription(
+ *event,
+ QString::fromStdString(http::client()->user_id().to_string()),
+ cache::displayName(room_id_, QString::fromStdString(mtx::accessors::sender(*event))));
+ if (description != lastMessage_) {
+ lastMessage_ = description;
+ emit lastMessageChanged();
}
+ return;
+ }
}
void
TimelineModel::setCurrentIndex(int index)
{
- auto oldIndex = idToIndex(currentId);
- currentId = indexToId(index);
- if (index != oldIndex)
- emit currentIndexChanged(index);
+ auto oldIndex = idToIndex(currentId);
+ currentId = indexToId(index);
+ if (index != oldIndex)
+ emit currentIndexChanged(index);
- if (!ChatPage::instance()->isActiveWindow())
- return;
+ if (!ChatPage::instance()->isActiveWindow())
+ return;
- if (!currentId.startsWith("m")) {
- auto oldReadIndex =
- cache::getEventIndex(roomId().toStdString(), currentReadId.toStdString());
- auto nextEventIndexAndId =
- cache::lastInvisibleEventAfter(roomId().toStdString(), currentId.toStdString());
+ if (!currentId.startsWith("m")) {
+ auto oldReadIndex =
+ cache::getEventIndex(roomId().toStdString(), currentReadId.toStdString());
+ auto nextEventIndexAndId =
+ cache::lastInvisibleEventAfter(roomId().toStdString(), currentId.toStdString());
- if (nextEventIndexAndId &&
- (!oldReadIndex || *oldReadIndex < nextEventIndexAndId->first)) {
- readEvent(nextEventIndexAndId->second);
- currentReadId = QString::fromStdString(nextEventIndexAndId->second);
- }
+ if (nextEventIndexAndId && (!oldReadIndex || *oldReadIndex < nextEventIndexAndId->first)) {
+ readEvent(nextEventIndexAndId->second);
+ currentReadId = QString::fromStdString(nextEventIndexAndId->second);
}
+ }
}
void
TimelineModel::readEvent(const std::string &id)
{
- http::client()->read_event(room_id_.toStdString(), id, [this](mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to read_event ({}, {})",
- room_id_.toStdString(),
- currentId.toStdString());
- }
- });
+ http::client()->read_event(room_id_.toStdString(), id, [this](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn(
+ "failed to read_event ({}, {})", room_id_.toStdString(), currentId.toStdString());
+ }
+ });
}
QString
TimelineModel::displayName(QString id) const
{
- return cache::displayName(room_id_, id).toHtmlEscaped();
+ return cache::displayName(room_id_, id).toHtmlEscaped();
}
QString
TimelineModel::avatarUrl(QString id) const
{
- return cache::avatarUrl(room_id_, id);
+ return cache::avatarUrl(room_id_, id);
}
QString
TimelineModel::formatDateSeparator(QDate date) const
{
- auto now = QDateTime::currentDateTime();
+ auto now = QDateTime::currentDateTime();
- QString fmt = QLocale::system().dateFormat(QLocale::LongFormat);
+ QString fmt = QLocale::system().dateFormat(QLocale::LongFormat);
- if (now.date().year() == date.year()) {
- QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*");
- fmt = fmt.remove(rx);
- }
+ if (now.date().year() == date.year()) {
+ QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*");
+ fmt = fmt.remove(rx);
+ }
- return date.toString(fmt);
+ return date.toString(fmt);
}
void
TimelineModel::viewRawMessage(QString id)
{
- auto e = events.get(id.toStdString(), "", false);
- if (!e)
- return;
- std::string ev = mtx::accessors::serialize_event(*e).dump(4);
- emit showRawMessageDialog(QString::fromStdString(ev));
+ auto e = events.get(id.toStdString(), "", false);
+ if (!e)
+ return;
+ std::string ev = mtx::accessors::serialize_event(*e).dump(4);
+ emit showRawMessageDialog(QString::fromStdString(ev));
}
void
TimelineModel::forwardMessage(QString eventId, QString roomId)
{
- auto e = events.get(eventId.toStdString(), "");
- if (!e)
- return;
+ auto e = events.get(eventId.toStdString(), "");
+ if (!e)
+ return;
- emit forwardToRoom(e, roomId);
+ emit forwardToRoom(e, roomId);
}
void
TimelineModel::viewDecryptedRawMessage(QString id)
{
- auto e = events.get(id.toStdString(), "");
- if (!e)
- return;
+ auto e = events.get(id.toStdString(), "");
+ if (!e)
+ return;
- std::string ev = mtx::accessors::serialize_event(*e).dump(4);
- emit showRawMessageDialog(QString::fromStdString(ev));
+ std::string ev = mtx::accessors::serialize_event(*e).dump(4);
+ emit showRawMessageDialog(QString::fromStdString(ev));
}
void
TimelineModel::openUserProfile(QString userid)
{
- UserProfile *userProfile = new UserProfile(room_id_, userid, manager_, this);
- connect(
- this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::updateAvatarUrl);
- emit manager_->openProfile(userProfile);
+ UserProfile *userProfile = new UserProfile(room_id_, userid, manager_, this);
+ connect(this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::updateAvatarUrl);
+ emit manager_->openProfile(userProfile);
}
void
TimelineModel::replyAction(QString id)
{
- setReply(id);
+ setReply(id);
}
void
TimelineModel::editAction(QString id)
{
- setEdit(id);
+ setEdit(id);
}
RelatedInfo
TimelineModel::relatedInfo(QString id)
{
- auto event = events.get(id.toStdString(), "");
- if (!event)
- return {};
+ auto event = events.get(id.toStdString(), "");
+ if (!event)
+ return {};
- return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_);
+ return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_);
}
void
TimelineModel::showReadReceipts(QString id)
{
- emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this});
+ emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this});
}
void
TimelineModel::redactEvent(QString id)
{
- if (!id.isEmpty())
- http::client()->redact_event(
- room_id_.toStdString(),
- id.toStdString(),
- [this, id](const mtx::responses::EventId &, mtx::http::RequestErr err) {
- if (err) {
- emit redactionFailed(
- tr("Message redaction failed: %1")
- .arg(QString::fromStdString(err->matrix_error.error)));
- return;
- }
-
- emit eventRedacted(id);
- });
+ if (!id.isEmpty())
+ http::client()->redact_event(
+ room_id_.toStdString(),
+ id.toStdString(),
+ [this, id](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit redactionFailed(tr("Message redaction failed: %1")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ return;
+ }
+
+ emit eventRedacted(id);
+ });
}
int
TimelineModel::idToIndex(QString id) const
{
- if (id.isEmpty())
- return -1;
+ if (id.isEmpty())
+ return -1;
- auto idx = events.idToIndex(id.toStdString());
- if (idx)
- return events.size() - *idx - 1;
- else
- return -1;
+ auto idx = events.idToIndex(id.toStdString());
+ if (idx)
+ return events.size() - *idx - 1;
+ else
+ return -1;
}
QString
TimelineModel::indexToId(int index) const
{
- auto id = events.indexToId(events.size() - index - 1);
- return id ? QString::fromStdString(*id) : "";
+ auto id = events.indexToId(events.size() - index - 1);
+ return id ? QString::fromStdString(*id) : "";
}
// Note: this will only be called for our messages
void
TimelineModel::markEventsAsRead(const std::vector<QString> &event_ids)
{
- for (const auto &id : event_ids) {
- read.insert(id);
- int idx = idToIndex(id);
- if (idx < 0) {
- return;
- }
- emit dataChanged(index(idx, 0), index(idx, 0));
+ for (const auto &id : event_ids) {
+ read.insert(id);
+ int idx = idToIndex(id);
+ if (idx < 0) {
+ return;
}
+ emit dataChanged(index(idx, 0), index(idx, 0));
+ }
}
template<typename T>
void
TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType)
{
- const auto room_id = room_id_.toStdString();
-
- using namespace mtx::events;
- using namespace mtx::identifiers;
-
- json doc = {{"type", mtx::events::to_string(eventType)},
- {"content", json(msg.content)},
- {"room_id", room_id}};
-
- try {
- mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
- event.content =
- olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
- event.event_id = msg.event_id;
- event.room_id = room_id;
- event.sender = http::client()->user_id().to_string();
- event.type = mtx::events::EventType::RoomEncrypted;
- event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
-
- emit this->addPendingMessageToStore(event);
-
- // TODO: Let the user know about the errors.
- } catch (const lmdb::error &e) {
- nhlog::db()->critical(
- "failed to open outbound megolm session ({}): {}", room_id, e.what());
- emit ChatPage::instance()->showNotification(
- tr("Failed to encrypt event, sending aborted!"));
- } catch (const mtx::crypto::olm_exception &e) {
- nhlog::crypto()->critical(
- "failed to open outbound megolm session ({}): {}", room_id, e.what());
- emit ChatPage::instance()->showNotification(
- tr("Failed to encrypt event, sending aborted!"));
- }
-}
-
-struct SendMessageVisitor
-{
- explicit SendMessageVisitor(TimelineModel *model)
- : model_(model)
- {}
-
- template<typename T, mtx::events::EventType Event>
- void sendRoomEvent(mtx::events::RoomEvent<T> msg)
- {
- if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
- auto encInfo = mtx::accessors::file(msg);
- if (encInfo)
- emit model_->newEncryptedImage(encInfo.value());
-
- model_->sendEncryptedMessage(msg, Event);
- } else {
- msg.type = Event;
- emit model_->addPendingMessageToStore(msg);
- }
- }
-
- // Do-nothing operator for all unhandled events
- template<typename T>
- void operator()(const mtx::events::Event<T> &)
- {}
-
- // Operator for m.room.message events that contain a msgtype in their content
- template<typename T,
- std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
- void operator()(mtx::events::RoomEvent<T> msg)
- {
- sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg);
- }
+ const auto room_id = room_id_.toStdString();
- // Special operator for reactions, which are a type of m.room.message, but need to be
- // handled distinctly for their differences from normal room messages. Specifically,
- // reactions need to have the relation outside of ciphertext, or synapse / the homeserver
- // cannot handle it correctly. See the MSC for more details:
- // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
- void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg)
- {
- msg.type = mtx::events::EventType::Reaction;
- emit model_->addPendingMessageToStore(msg);
- }
-
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event)
- {
- sendRoomEvent<mtx::events::msg::CallInvite, mtx::events::EventType::CallInvite>(
- event);
- }
-
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &event)
- {
- sendRoomEvent<mtx::events::msg::CallCandidates,
- mtx::events::EventType::CallCandidates>(event);
- }
+ using namespace mtx::events;
+ using namespace mtx::identifiers;
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &event)
- {
- sendRoomEvent<mtx::events::msg::CallAnswer, mtx::events::EventType::CallAnswer>(
- event);
- }
+ json doc = {{"type", mtx::events::to_string(eventType)},
+ {"content", json(msg.content)},
+ {"room_id", room_id}};
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &event)
- {
- sendRoomEvent<mtx::events::msg::CallHangUp, mtx::events::EventType::CallHangUp>(
- event);
- }
+ try {
+ mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
+ event.content = olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
+ event.event_id = msg.event_id;
+ event.room_id = room_id;
+ event.sender = http::client()->user_id().to_string();
+ event.type = mtx::events::EventType::RoomEncrypted;
+ event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationRequest,
- mtx::events::EventType::RoomMessage>(msg);
- }
+ emit this->addPendingMessageToStore(event);
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationReady,
- mtx::events::EventType::KeyVerificationReady>(msg);
- }
-
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationStart,
- mtx::events::EventType::KeyVerificationStart>(msg);
- }
-
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationAccept,
- mtx::events::EventType::KeyVerificationAccept>(msg);
- }
-
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationMac,
- mtx::events::EventType::KeyVerificationMac>(msg);
- }
+ // TODO: Let the user know about the errors.
+ } catch (const lmdb::error &e) {
+ nhlog::db()->critical("failed to open outbound megolm session ({}): {}", room_id, e.what());
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to encrypt event, sending aborted!"));
+ } catch (const mtx::crypto::olm_exception &e) {
+ nhlog::crypto()->critical(
+ "failed to open outbound megolm session ({}): {}", room_id, e.what());
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to encrypt event, sending aborted!"));
+ }
+}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationKey,
- mtx::events::EventType::KeyVerificationKey>(msg);
- }
+struct SendMessageVisitor
+{
+ explicit SendMessageVisitor(TimelineModel *model)
+ : model_(model)
+ {}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationDone,
- mtx::events::EventType::KeyVerificationDone>(msg);
- }
+ template<typename T, mtx::events::EventType Event>
+ void sendRoomEvent(mtx::events::RoomEvent<T> msg)
+ {
+ if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
+ auto encInfo = mtx::accessors::file(msg);
+ if (encInfo)
+ emit model_->newEncryptedImage(encInfo.value());
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg)
- {
- sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
- mtx::events::EventType::KeyVerificationCancel>(msg);
- }
- void operator()(mtx::events::Sticker msg)
- {
- msg.type = mtx::events::EventType::Sticker;
- if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
- model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker);
- } else
- emit model_->addPendingMessageToStore(msg);
- }
+ model_->sendEncryptedMessage(msg, Event);
+ } else {
+ msg.type = Event;
+ emit model_->addPendingMessageToStore(msg);
+ }
+ }
+
+ // Do-nothing operator for all unhandled events
+ template<typename T>
+ void operator()(const mtx::events::Event<T> &)
+ {}
+
+ // Operator for m.room.message events that contain a msgtype in their content
+ template<typename T,
+ std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
+ void operator()(mtx::events::RoomEvent<T> msg)
+ {
+ sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg);
+ }
+
+ // Special operator for reactions, which are a type of m.room.message, but need to be
+ // handled distinctly for their differences from normal room messages. Specifically,
+ // reactions need to have the relation outside of ciphertext, or synapse / the homeserver
+ // cannot handle it correctly. See the MSC for more details:
+ // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
+ void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg)
+ {
+ msg.type = mtx::events::EventType::Reaction;
+ emit model_->addPendingMessageToStore(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallInvite, mtx::events::EventType::CallInvite>(event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallCandidates, mtx::events::EventType::CallCandidates>(
+ event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallAnswer, mtx::events::EventType::CallAnswer>(event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallHangUp, mtx::events::EventType::CallHangUp>(event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationRequest,
+ mtx::events::EventType::RoomMessage>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationReady,
+ mtx::events::EventType::KeyVerificationReady>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationStart,
+ mtx::events::EventType::KeyVerificationStart>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationAccept,
+ mtx::events::EventType::KeyVerificationAccept>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationMac,
+ mtx::events::EventType::KeyVerificationMac>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationKey,
+ mtx::events::EventType::KeyVerificationKey>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationDone,
+ mtx::events::EventType::KeyVerificationDone>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
+ mtx::events::EventType::KeyVerificationCancel>(msg);
+ }
+ void operator()(mtx::events::Sticker msg)
+ {
+ msg.type = mtx::events::EventType::Sticker;
+ if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
+ model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker);
+ } else
+ emit model_->addPendingMessageToStore(msg);
+ }
- TimelineModel *model_;
+ TimelineModel *model_;
};
void
TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
{
- std::visit(
- [](auto &msg) {
- // gets overwritten for reactions and stickers in SendMessageVisitor
- msg.type = mtx::events::EventType::RoomMessage;
- msg.event_id = "m" + http::client()->generate_txn_id();
- msg.sender = http::client()->user_id().to_string();
- msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
- },
- event);
+ std::visit(
+ [](auto &msg) {
+ // gets overwritten for reactions and stickers in SendMessageVisitor
+ msg.type = mtx::events::EventType::RoomMessage;
+ msg.event_id = "m" + http::client()->generate_txn_id();
+ msg.sender = http::client()->user_id().to_string();
+ msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
+ },
+ event);
- std::visit(SendMessageVisitor{this}, event);
+ std::visit(SendMessageVisitor{this}, event);
}
void
TimelineModel::openMedia(QString eventId)
{
- cacheMedia(eventId, [](QString filename) {
- QDesktopServices::openUrl(QUrl::fromLocalFile(filename));
- });
+ cacheMedia(eventId,
+ [](QString filename) { QDesktopServices::openUrl(QUrl::fromLocalFile(filename)); });
}
bool
TimelineModel::saveMedia(QString eventId) const
{
- mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), "");
- if (!event)
- return false;
+ mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), "");
+ if (!event)
+ return false;
- QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
- QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
- QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
+ QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
+ QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
+ QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
- auto encryptionInfo = mtx::accessors::file(*event);
+ auto encryptionInfo = mtx::accessors::file(*event);
- qml_mtx_events::EventType eventType = toRoomEventType(*event);
+ qml_mtx_events::EventType eventType = toRoomEventType(*event);
- QString dialogTitle;
- if (eventType == qml_mtx_events::EventType::ImageMessage) {
- dialogTitle = tr("Save image");
- } else if (eventType == qml_mtx_events::EventType::VideoMessage) {
- dialogTitle = tr("Save video");
- } else if (eventType == qml_mtx_events::EventType::AudioMessage) {
- dialogTitle = tr("Save audio");
- } else {
- dialogTitle = tr("Save file");
- }
+ QString dialogTitle;
+ if (eventType == qml_mtx_events::EventType::ImageMessage) {
+ dialogTitle = tr("Save image");
+ } else if (eventType == qml_mtx_events::EventType::VideoMessage) {
+ dialogTitle = tr("Save video");
+ } else if (eventType == qml_mtx_events::EventType::AudioMessage) {
+ dialogTitle = tr("Save audio");
+ } else {
+ dialogTitle = tr("Save file");
+ }
- const QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString();
- const QString downloadsFolder =
- QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
- const QString openLocation = downloadsFolder + "/" + originalFilename;
-
- const QString filename = QFileDialog::getSaveFileName(
- manager_->getWidget(), dialogTitle, openLocation, filterString);
-
- if (filename.isEmpty())
- return false;
-
- const auto url = mxcUrl.toStdString();
-
- http::client()->download(
- url,
- [filename, url, encryptionInfo](const std::string &data,
- const std::string &,
- const std::string &,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve image {}: {} {}",
- url,
- err->matrix_error.error,
- static_cast<int>(err->status_code));
- return;
- }
+ const QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString();
+ const QString downloadsFolder =
+ QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+ const QString openLocation = downloadsFolder + "/" + originalFilename;
- try {
- auto temp = data;
- if (encryptionInfo)
- temp = mtx::crypto::to_string(
- mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
+ const QString filename =
+ QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString);
- QFile file(filename);
+ if (filename.isEmpty())
+ return false;
- if (!file.open(QIODevice::WriteOnly))
- return;
+ const auto url = mxcUrl.toStdString();
- file.write(QByteArray(temp.data(), (int)temp.size()));
- file.close();
+ http::client()->download(url,
+ [filename, url, encryptionInfo](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve image {}: {} {}",
+ url,
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
- return;
- } catch (const std::exception &e) {
- nhlog::ui()->warn("Error while saving file to: {}", e.what());
- }
- });
- return true;
+ try {
+ auto temp = data;
+ if (encryptionInfo)
+ temp = mtx::crypto::to_string(
+ mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
+
+ QFile file(filename);
+
+ if (!file.open(QIODevice::WriteOnly))
+ return;
+
+ file.write(QByteArray(temp.data(), (int)temp.size()));
+ file.close();
+
+ return;
+ } catch (const std::exception &e) {
+ nhlog::ui()->warn("Error while saving file to: {}", e.what());
+ }
+ });
+ return true;
}
void
TimelineModel::cacheMedia(QString eventId, std::function<void(const QString)> callback)
{
- mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), "");
- if (!event)
- return;
+ mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), "");
+ if (!event)
+ return;
- QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
- QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
- QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
+ QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
+ QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
+ QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
- auto encryptionInfo = mtx::accessors::file(*event);
+ auto encryptionInfo = mtx::accessors::file(*event);
- // If the message is a link to a non mxcUrl, don't download it
- if (!mxcUrl.startsWith("mxc://")) {
- emit mediaCached(mxcUrl, mxcUrl);
- return;
- }
-
- QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
-
- const auto url = mxcUrl.toStdString();
- const auto name = QString(mxcUrl).remove("mxc://");
- QFileInfo filename(QString("%1/media_cache/%2.%3")
- .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(name)
- .arg(suffix));
- if (QDir::cleanPath(name) != name) {
- nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
- return;
- }
+ // If the message is a link to a non mxcUrl, don't download it
+ if (!mxcUrl.startsWith("mxc://")) {
+ emit mediaCached(mxcUrl, mxcUrl);
+ return;
+ }
+
+ QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
+
+ const auto url = mxcUrl.toStdString();
+ const auto name = QString(mxcUrl).remove("mxc://");
+ QFileInfo filename(QString("%1/media_cache/%2.%3")
+ .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
+ .arg(name)
+ .arg(suffix));
+ if (QDir::cleanPath(name) != name) {
+ nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
+ return;
+ }
- QDir().mkpath(filename.path());
+ QDir().mkpath(filename.path());
- if (filename.isReadable()) {
+ if (filename.isReadable()) {
#if defined(Q_OS_WIN)
- emit mediaCached(mxcUrl, filename.filePath());
+ emit mediaCached(mxcUrl, filename.filePath());
#else
- emit mediaCached(mxcUrl, "file://" + filename.filePath());
+ emit mediaCached(mxcUrl, "file://" + filename.filePath());
#endif
- if (callback) {
- callback(filename.filePath());
- }
- return;
+ if (callback) {
+ callback(filename.filePath());
}
-
- http::client()->download(
- url,
- [this, callback, mxcUrl, filename, url, encryptionInfo](const std::string &data,
- const std::string &,
- const std::string &,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve image {}: {} {}",
- url,
- err->matrix_error.error,
- static_cast<int>(err->status_code));
- return;
- }
-
- try {
- auto temp = data;
- if (encryptionInfo)
- temp = mtx::crypto::to_string(
- mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
-
- QFile file(filename.filePath());
-
- if (!file.open(QIODevice::WriteOnly))
- return;
-
- file.write(QByteArray(temp.data(), (int)temp.size()));
- file.close();
-
- if (callback) {
- callback(filename.filePath());
- }
- } catch (const std::exception &e) {
- nhlog::ui()->warn("Error while saving file to: {}", e.what());
- }
+ return;
+ }
+
+ http::client()->download(
+ url,
+ [this, callback, mxcUrl, filename, url, encryptionInfo](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve image {}: {} {}",
+ url,
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ try {
+ auto temp = data;
+ if (encryptionInfo)
+ temp =
+ mtx::crypto::to_string(mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
+
+ QFile file(filename.filePath());
+
+ if (!file.open(QIODevice::WriteOnly))
+ return;
+
+ file.write(QByteArray(temp.data(), (int)temp.size()));
+ file.close();
+
+ if (callback) {
+ callback(filename.filePath());
+ }
+ } catch (const std::exception &e) {
+ nhlog::ui()->warn("Error while saving file to: {}", e.what());
+ }
#if defined(Q_OS_WIN)
- emit mediaCached(mxcUrl, filename.filePath());
+ emit mediaCached(mxcUrl, filename.filePath());
#else
- emit mediaCached(mxcUrl, "file://" + filename.filePath());
+ emit mediaCached(mxcUrl, "file://" + filename.filePath());
#endif
- });
+ });
}
void
TimelineModel::cacheMedia(QString eventId)
{
- cacheMedia(eventId, NULL);
+ cacheMedia(eventId, NULL);
}
void
TimelineModel::showEvent(QString eventId)
{
- using namespace std::chrono_literals;
- // Direct to eventId
- if (eventId[0] == '$') {
- int idx = idToIndex(eventId);
- if (idx == -1) {
- nhlog::ui()->warn("Scrolling to event id {}, failed - no known index",
- eventId.toStdString());
- return;
- }
- eventIdToShow = eventId;
- emit scrollTargetChanged();
- showEventTimer.start(50ms);
- return;
+ using namespace std::chrono_literals;
+ // Direct to eventId
+ if (eventId[0] == '$') {
+ int idx = idToIndex(eventId);
+ if (idx == -1) {
+ nhlog::ui()->warn("Scrolling to event id {}, failed - no known index",
+ eventId.toStdString());
+ return;
}
- // to message index
- eventId = indexToId(eventId.toInt());
eventIdToShow = eventId;
emit scrollTargetChanged();
showEventTimer.start(50ms);
return;
+ }
+ // to message index
+ eventId = indexToId(eventId.toInt());
+ eventIdToShow = eventId;
+ emit scrollTargetChanged();
+ showEventTimer.start(50ms);
+ return;
}
void
TimelineModel::eventShown()
{
- eventIdToShow.clear();
- emit scrollTargetChanged();
+ eventIdToShow.clear();
+ emit scrollTargetChanged();
}
QString
TimelineModel::scrollTarget() const
{
- return eventIdToShow;
+ return eventIdToShow;
}
void
TimelineModel::scrollTimerEvent()
{
- if (eventIdToShow.isEmpty() || showEventTimerCounter > 3) {
- showEventTimer.stop();
- showEventTimerCounter = 0;
- } else {
- emit scrollToIndex(idToIndex(eventIdToShow));
- showEventTimerCounter++;
- }
+ if (eventIdToShow.isEmpty() || showEventTimerCounter > 3) {
+ showEventTimer.stop();
+ showEventTimerCounter = 0;
+ } else {
+ emit scrollToIndex(idToIndex(eventIdToShow));
+ showEventTimerCounter++;
+ }
}
void
TimelineModel::requestKeyForEvent(QString id)
{
- auto encrypted_event = events.get(id.toStdString(), "", false);
- if (encrypted_event) {
- if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- encrypted_event))
- events.requestSession(*ev, true);
- }
+ auto encrypted_event = events.get(id.toStdString(), "", false);
+ if (encrypted_event) {
+ if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ encrypted_event))
+ events.requestSession(*ev, true);
+ }
}
void
TimelineModel::copyLinkToEvent(QString eventId) const
{
- QStringList vias;
+ QStringList vias;
- auto alias = cache::client()->getRoomAliases(room_id_.toStdString());
- QString room;
- if (alias) {
- room = QString::fromStdString(alias->alias);
- if (room.isEmpty() && !alias->alt_aliases.empty()) {
- room = QString::fromStdString(alias->alt_aliases.front());
- }
+ auto alias = cache::client()->getRoomAliases(room_id_.toStdString());
+ QString room;
+ if (alias) {
+ room = QString::fromStdString(alias->alias);
+ if (room.isEmpty() && !alias->alt_aliases.empty()) {
+ room = QString::fromStdString(alias->alt_aliases.front());
}
+ }
- if (room.isEmpty())
- room = room_id_;
+ if (room.isEmpty())
+ room = room_id_;
- vias.push_back(QString("via=%1").arg(QString(
- QUrl::toPercentEncoding(QString::fromStdString(http::client()->user_id().hostname())))));
- auto members = cache::getMembers(room_id_.toStdString(), 0, 100);
- for (const auto &m : members) {
- if (vias.size() >= 4)
- break;
+ vias.push_back(QString("via=%1").arg(QString(
+ QUrl::toPercentEncoding(QString::fromStdString(http::client()->user_id().hostname())))));
+ auto members = cache::getMembers(room_id_.toStdString(), 0, 100);
+ for (const auto &m : members) {
+ if (vias.size() >= 4)
+ break;
- auto user_id =
- mtx::identifiers::parse<mtx::identifiers::User>(m.user_id.toStdString());
- QString server = QString("via=%1").arg(
- QString(QUrl::toPercentEncoding(QString::fromStdString(user_id.hostname()))));
+ auto user_id = mtx::identifiers::parse<mtx::identifiers::User>(m.user_id.toStdString());
+ QString server = QString("via=%1").arg(
+ QString(QUrl::toPercentEncoding(QString::fromStdString(user_id.hostname()))));
- if (!vias.contains(server))
- vias.push_back(server);
- }
+ if (!vias.contains(server))
+ vias.push_back(server);
+ }
- auto link = QString("https://matrix.to/#/%1/%2?%3")
- .arg(QString(QUrl::toPercentEncoding(room)),
- QString(QUrl::toPercentEncoding(eventId)),
- vias.join('&'));
+ auto link = QString("https://matrix.to/#/%1/%2?%3")
+ .arg(QString(QUrl::toPercentEncoding(room)),
+ QString(QUrl::toPercentEncoding(eventId)),
+ vias.join('&'));
- QGuiApplication::clipboard()->setText(link);
+ QGuiApplication::clipboard()->setText(link);
}
QString
TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg)
{
- QString temp =
- tr("%1 and %2 are typing.",
- "Multiple users are typing. First argument is a comma separated list of potentially "
- "multiple users. Second argument is the last user of that list. (If only one user is "
- "typing, %1 is empty. You should still use it in your string though to silence Qt "
- "warnings.)",
- (int)users.size());
+ QString temp =
+ tr("%1 and %2 are typing.",
+ "Multiple users are typing. First argument is a comma separated list of potentially "
+ "multiple users. Second argument is the last user of that list. (If only one user is "
+ "typing, %1 is empty. You should still use it in your string though to silence Qt "
+ "warnings.)",
+ (int)users.size());
- if (users.empty()) {
- return "";
- }
+ if (users.empty()) {
+ return "";
+ }
- QStringList uidWithoutLast;
+ QStringList uidWithoutLast;
- auto formatUser = [this, bg](const QString &user_id) -> QString {
- auto uncoloredUsername = utils::replaceEmoji(displayName(user_id));
- QString prefix =
- QString("<font color=\"%1\">").arg(manager_->userColor(user_id, bg).name());
+ auto formatUser = [this, bg](const QString &user_id) -> QString {
+ auto uncoloredUsername = utils::replaceEmoji(displayName(user_id));
+ QString prefix =
+ QString("<font color=\"%1\">").arg(manager_->userColor(user_id, bg).name());
- // color only parts that don't have a font already specified
- QString coloredUsername;
- int index = 0;
- do {
- auto startIndex = uncoloredUsername.indexOf("<font", index);
+ // color only parts that don't have a font already specified
+ QString coloredUsername;
+ int index = 0;
+ do {
+ auto startIndex = uncoloredUsername.indexOf("<font", index);
- if (startIndex - index != 0)
- coloredUsername +=
- prefix +
- uncoloredUsername.midRef(
- index, startIndex > 0 ? startIndex - index : -1) +
- "</font>";
+ if (startIndex - index != 0)
+ coloredUsername +=
+ prefix +
+ uncoloredUsername.midRef(index, startIndex > 0 ? startIndex - index : -1) +
+ "</font>";
- auto endIndex = uncoloredUsername.indexOf("</font>", startIndex);
- if (endIndex > 0)
- endIndex += sizeof("</font>") - 1;
+ auto endIndex = uncoloredUsername.indexOf("</font>", startIndex);
+ if (endIndex > 0)
+ endIndex += sizeof("</font>") - 1;
- if (endIndex - startIndex != 0)
- coloredUsername +=
- uncoloredUsername.midRef(startIndex, endIndex - startIndex);
+ if (endIndex - startIndex != 0)
+ coloredUsername += uncoloredUsername.midRef(startIndex, endIndex - startIndex);
- index = endIndex;
- } while (index > 0 && index < uncoloredUsername.size());
+ index = endIndex;
+ } while (index > 0 && index < uncoloredUsername.size());
- return coloredUsername;
- };
+ return coloredUsername;
+ };
- for (size_t i = 0; i + 1 < users.size(); i++) {
- uidWithoutLast.append(formatUser(users[i]));
- }
+ for (size_t i = 0; i + 1 < users.size(); i++) {
+ uidWithoutLast.append(formatUser(users[i]));
+ }
- return temp.arg(uidWithoutLast.join(", ")).arg(formatUser(users.back()));
+ return temp.arg(uidWithoutLast.join(", ")).arg(formatUser(users.back()));
}
QString
TimelineModel::formatJoinRuleEvent(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return "";
-
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::JoinRules>>(e);
- if (!event)
- return "";
-
- QString user = QString::fromStdString(event->sender);
- QString name = utils::replaceEmoji(displayName(user));
-
- switch (event->content.join_rule) {
- case mtx::events::state::JoinRule::Public:
- return tr("%1 opened the room to the public.").arg(name);
- case mtx::events::state::JoinRule::Invite:
- return tr("%1 made this room require and invitation to join.").arg(name);
- case mtx::events::state::JoinRule::Knock:
- return tr("%1 allowed to join this room by knocking.").arg(name);
- case mtx::events::state::JoinRule::Restricted: {
- QStringList rooms;
- for (const auto &r : event->content.allow) {
- if (r.type == mtx::events::state::JoinAllowanceType::RoomMembership)
- rooms.push_back(QString::fromStdString(r.room_id));
- }
- return tr("%1 allowed members of the following rooms to automatically join this "
- "room: %2")
- .arg(name)
- .arg(rooms.join(", "));
- }
- default:
- // Currently, knock and private are reserved keywords and not implemented in Matrix.
- return "";
- }
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return "";
+
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::JoinRules>>(e);
+ if (!event)
+ return "";
+
+ QString user = QString::fromStdString(event->sender);
+ QString name = utils::replaceEmoji(displayName(user));
+
+ switch (event->content.join_rule) {
+ case mtx::events::state::JoinRule::Public:
+ return tr("%1 opened the room to the public.").arg(name);
+ case mtx::events::state::JoinRule::Invite:
+ return tr("%1 made this room require and invitation to join.").arg(name);
+ case mtx::events::state::JoinRule::Knock:
+ return tr("%1 allowed to join this room by knocking.").arg(name);
+ case mtx::events::state::JoinRule::Restricted: {
+ QStringList rooms;
+ for (const auto &r : event->content.allow) {
+ if (r.type == mtx::events::state::JoinAllowanceType::RoomMembership)
+ rooms.push_back(QString::fromStdString(r.room_id));
+ }
+ return tr("%1 allowed members of the following rooms to automatically join this "
+ "room: %2")
+ .arg(name)
+ .arg(rooms.join(", "));
+ }
+ default:
+ // Currently, knock and private are reserved keywords and not implemented in Matrix.
+ return "";
+ }
}
QString
TimelineModel::formatGuestAccessEvent(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return "";
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return "";
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::GuestAccess>>(e);
- if (!event)
- return "";
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::GuestAccess>>(e);
+ if (!event)
+ return "";
- QString user = QString::fromStdString(event->sender);
- QString name = utils::replaceEmoji(displayName(user));
+ QString user = QString::fromStdString(event->sender);
+ QString name = utils::replaceEmoji(displayName(user));
- switch (event->content.guest_access) {
- case mtx::events::state::AccessState::CanJoin:
- return tr("%1 made the room open to guests.").arg(name);
- case mtx::events::state::AccessState::Forbidden:
- return tr("%1 has closed the room to guest access.").arg(name);
- default:
- return "";
- }
+ switch (event->content.guest_access) {
+ case mtx::events::state::AccessState::CanJoin:
+ return tr("%1 made the room open to guests.").arg(name);
+ case mtx::events::state::AccessState::Forbidden:
+ return tr("%1 has closed the room to guest access.").arg(name);
+ default:
+ return "";
+ }
}
QString
TimelineModel::formatHistoryVisibilityEvent(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return "";
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return "";
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>(e);
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>(e);
- if (!event)
- return "";
-
- QString user = QString::fromStdString(event->sender);
- QString name = utils::replaceEmoji(displayName(user));
-
- switch (event->content.history_visibility) {
- case mtx::events::state::Visibility::WorldReadable:
- return tr("%1 made the room history world readable. Events may be now read by "
- "non-joined people.")
- .arg(name);
- case mtx::events::state::Visibility::Shared:
- return tr("%1 set the room history visible to members from this point on.")
- .arg(name);
- case mtx::events::state::Visibility::Invited:
- return tr("%1 set the room history visible to members since they were invited.")
- .arg(name);
- case mtx::events::state::Visibility::Joined:
- return tr("%1 set the room history visible to members since they joined the room.")
- .arg(name);
- default:
- return "";
- }
+ if (!event)
+ return "";
+
+ QString user = QString::fromStdString(event->sender);
+ QString name = utils::replaceEmoji(displayName(user));
+
+ switch (event->content.history_visibility) {
+ case mtx::events::state::Visibility::WorldReadable:
+ return tr("%1 made the room history world readable. Events may be now read by "
+ "non-joined people.")
+ .arg(name);
+ case mtx::events::state::Visibility::Shared:
+ return tr("%1 set the room history visible to members from this point on.").arg(name);
+ case mtx::events::state::Visibility::Invited:
+ return tr("%1 set the room history visible to members since they were invited.").arg(name);
+ case mtx::events::state::Visibility::Joined:
+ return tr("%1 set the room history visible to members since they joined the room.")
+ .arg(name);
+ default:
+ return "";
+ }
}
QString
TimelineModel::formatPowerLevelEvent(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return "";
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return "";
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::PowerLevels>>(e);
- if (!event)
- return "";
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::PowerLevels>>(e);
+ if (!event)
+ return "";
- QString user = QString::fromStdString(event->sender);
- QString name = utils::replaceEmoji(displayName(user));
+ QString user = QString::fromStdString(event->sender);
+ QString name = utils::replaceEmoji(displayName(user));
- // TODO: power levels rendering is actually a bit complex. work on this later.
- return tr("%1 has changed the room's permissions.").arg(name);
+ // TODO: power levels rendering is actually a bit complex. work on this later.
+ return tr("%1 has changed the room's permissions.").arg(name);
}
void
TimelineModel::acceptKnock(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return;
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return;
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
- if (!event)
- return;
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
+ if (!event)
+ return;
- if (!permissions_.canInvite())
- return;
+ if (!permissions_.canInvite())
+ return;
- if (cache::isRoomMember(event->state_key, room_id_.toStdString()))
- return;
+ if (cache::isRoomMember(event->state_key, room_id_.toStdString()))
+ return;
- using namespace mtx::events::state;
- if (event->content.membership != Membership::Knock)
- return;
+ using namespace mtx::events::state;
+ if (event->content.membership != Membership::Knock)
+ return;
- ChatPage::instance()->inviteUser(QString::fromStdString(event->state_key), "");
+ ChatPage::instance()->inviteUser(QString::fromStdString(event->state_key), "");
}
bool
TimelineModel::showAcceptKnockButton(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return false;
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return false;
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
- if (!event)
- return false;
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
+ if (!event)
+ return false;
- if (!permissions_.canInvite())
- return false;
+ if (!permissions_.canInvite())
+ return false;
- if (cache::isRoomMember(event->state_key, room_id_.toStdString()))
- return false;
+ if (cache::isRoomMember(event->state_key, room_id_.toStdString()))
+ return false;
- using namespace mtx::events::state;
- return event->content.membership == Membership::Knock;
+ using namespace mtx::events::state;
+ return event->content.membership == Membership::Knock;
}
QString
TimelineModel::formatMemberEvent(QString id)
{
- mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
- if (!e)
- return "";
-
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
- if (!event)
- return "";
-
- mtx::events::StateEvent<mtx::events::state::Member> *prevEvent = nullptr;
- if (!event->unsigned_data.replaces_state.empty()) {
- auto tempPrevEvent =
- events.get(event->unsigned_data.replaces_state, event->event_id);
- if (tempPrevEvent) {
- prevEvent =
- std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(
- tempPrevEvent);
- }
- }
+ mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), "");
+ if (!e)
+ return "";
+
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
+ if (!event)
+ return "";
+
+ mtx::events::StateEvent<mtx::events::state::Member> *prevEvent = nullptr;
+ if (!event->unsigned_data.replaces_state.empty()) {
+ auto tempPrevEvent = events.get(event->unsigned_data.replaces_state, event->event_id);
+ if (tempPrevEvent) {
+ prevEvent =
+ std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(tempPrevEvent);
+ }
+ }
+
+ QString user = QString::fromStdString(event->state_key);
+ QString name = utils::replaceEmoji(displayName(user));
+ QString rendered;
+
+ // see table https://matrix.org/docs/spec/client_server/latest#m-room-member
+ using namespace mtx::events::state;
+ switch (event->content.membership) {
+ case Membership::Invite:
+ rendered = tr("%1 was invited.").arg(name);
+ break;
+ case Membership::Join:
+ if (prevEvent && prevEvent->content.membership == Membership::Join) {
+ QString oldName = utils::replaceEmoji(
+ QString::fromStdString(prevEvent->content.display_name).toHtmlEscaped());
+
+ bool displayNameChanged =
+ prevEvent->content.display_name != event->content.display_name;
+ bool avatarChanged = prevEvent->content.avatar_url != event->content.avatar_url;
+
+ if (displayNameChanged && avatarChanged)
+ rendered = tr("%1 has changed their avatar and changed their "
+ "display name to %2.")
+ .arg(oldName, name);
+ else if (displayNameChanged)
+ rendered = tr("%1 has changed their display name to %2.").arg(oldName, name);
+ else if (avatarChanged)
+ rendered = tr("%1 changed their avatar.").arg(name);
+ else
+ rendered = tr("%1 changed some profile info.").arg(name);
+ // the case of nothing changed but join follows join shouldn't happen, so
+ // just show it as join
+ } else {
+ if (event->content.join_authorised_via_users_server.empty())
+ rendered = tr("%1 joined.").arg(name);
+ else
+ rendered =
+ tr("%1 joined via authorisation from %2's server.")
+ .arg(name)
+ .arg(QString::fromStdString(event->content.join_authorised_via_users_server));
+ }
+ break;
+ case Membership::Leave:
+ if (!prevEvent) // Should only ever happen temporarily
+ return "";
+
+ if (prevEvent->content.membership == Membership::Invite) {
+ if (event->state_key == event->sender)
+ rendered = tr("%1 rejected their invite.").arg(name);
+ else
+ rendered = tr("Revoked the invite to %1.").arg(name);
+ } else if (prevEvent->content.membership == Membership::Join) {
+ if (event->state_key == event->sender)
+ rendered = tr("%1 left the room.").arg(name);
+ else
+ rendered = tr("Kicked %1.").arg(name);
+ } else if (prevEvent->content.membership == Membership::Ban) {
+ rendered = tr("Unbanned %1.").arg(name);
+ } else if (prevEvent->content.membership == Membership::Knock) {
+ if (event->state_key == event->sender)
+ rendered = tr("%1 redacted their knock.").arg(name);
+ else
+ rendered = tr("Rejected the knock from %1.").arg(name);
+ } else
+ return tr("%1 left after having already left!",
+ "This is a leave event after the user already left and shouldn't "
+ "happen apart from state resets")
+ .arg(name);
+ break;
- QString user = QString::fromStdString(event->state_key);
- QString name = utils::replaceEmoji(displayName(user));
- QString rendered;
-
- // see table https://matrix.org/docs/spec/client_server/latest#m-room-member
- using namespace mtx::events::state;
- switch (event->content.membership) {
- case Membership::Invite:
- rendered = tr("%1 was invited.").arg(name);
- break;
- case Membership::Join:
- if (prevEvent && prevEvent->content.membership == Membership::Join) {
- QString oldName = utils::replaceEmoji(
- QString::fromStdString(prevEvent->content.display_name).toHtmlEscaped());
-
- bool displayNameChanged =
- prevEvent->content.display_name != event->content.display_name;
- bool avatarChanged =
- prevEvent->content.avatar_url != event->content.avatar_url;
-
- if (displayNameChanged && avatarChanged)
- rendered = tr("%1 has changed their avatar and changed their "
- "display name to %2.")
- .arg(oldName, name);
- else if (displayNameChanged)
- rendered =
- tr("%1 has changed their display name to %2.").arg(oldName, name);
- else if (avatarChanged)
- rendered = tr("%1 changed their avatar.").arg(name);
- else
- rendered = tr("%1 changed some profile info.").arg(name);
- // the case of nothing changed but join follows join shouldn't happen, so
- // just show it as join
- } else {
- if (event->content.join_authorised_via_users_server.empty())
- rendered = tr("%1 joined.").arg(name);
- else
- rendered = tr("%1 joined via authorisation from %2's server.")
- .arg(name)
- .arg(QString::fromStdString(
- event->content.join_authorised_via_users_server));
- }
- break;
- case Membership::Leave:
- if (!prevEvent) // Should only ever happen temporarily
- return "";
-
- if (prevEvent->content.membership == Membership::Invite) {
- if (event->state_key == event->sender)
- rendered = tr("%1 rejected their invite.").arg(name);
- else
- rendered = tr("Revoked the invite to %1.").arg(name);
- } else if (prevEvent->content.membership == Membership::Join) {
- if (event->state_key == event->sender)
- rendered = tr("%1 left the room.").arg(name);
- else
- rendered = tr("Kicked %1.").arg(name);
- } else if (prevEvent->content.membership == Membership::Ban) {
- rendered = tr("Unbanned %1.").arg(name);
- } else if (prevEvent->content.membership == Membership::Knock) {
- if (event->state_key == event->sender)
- rendered = tr("%1 redacted their knock.").arg(name);
- else
- rendered = tr("Rejected the knock from %1.").arg(name);
- } else
- return tr("%1 left after having already left!",
- "This is a leave event after the user already left and shouldn't "
- "happen apart from state resets")
- .arg(name);
- break;
-
- case Membership::Ban:
- rendered = tr("%1 was banned.").arg(name);
- break;
- case Membership::Knock:
- rendered = tr("%1 knocked.").arg(name);
- break;
- }
+ case Membership::Ban:
+ rendered = tr("%1 was banned.").arg(name);
+ break;
+ case Membership::Knock:
+ rendered = tr("%1 knocked.").arg(name);
+ break;
+ }
- if (event->content.reason != "") {
- rendered +=
- " " + tr("Reason: %1").arg(QString::fromStdString(event->content.reason));
- }
+ if (event->content.reason != "") {
+ rendered += " " + tr("Reason: %1").arg(QString::fromStdString(event->content.reason));
+ }
- return rendered;
+ return rendered;
}
void
TimelineModel::setEdit(QString newEdit)
{
- if (newEdit.isEmpty()) {
- resetEdit();
- return;
- }
+ if (newEdit.isEmpty()) {
+ resetEdit();
+ return;
+ }
+
+ if (edit_.isEmpty()) {
+ this->textBeforeEdit = input()->text();
+ this->replyBeforeEdit = reply_;
+ nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString());
+ }
+
+ if (edit_ != newEdit) {
+ auto ev = events.get(newEdit.toStdString(), "");
+ if (ev && mtx::accessors::sender(*ev) == http::client()->user_id().to_string()) {
+ auto e = *ev;
+ setReply(QString::fromStdString(mtx::accessors::relations(e).reply_to().value_or("")));
+
+ auto msgType = mtx::accessors::msg_type(e);
+ if (msgType == mtx::events::MessageType::Text ||
+ msgType == mtx::events::MessageType::Notice ||
+ msgType == mtx::events::MessageType::Emote) {
+ auto relInfo = relatedInfo(newEdit);
+ auto editText = relInfo.quoted_body;
+
+ if (!relInfo.quoted_formatted_body.isEmpty()) {
+ auto matches =
+ conf::strings::matrixToLink.globalMatch(relInfo.quoted_formatted_body);
+ std::map<QString, QString> reverseNameMapping;
+ while (matches.hasNext()) {
+ auto m = matches.next();
+ reverseNameMapping[m.captured(2)] = m.captured(1);
+ }
+
+ for (const auto &[user, link] : reverseNameMapping) {
+ // TODO(Nico): html unescape the user name
+ editText.replace(user, QStringLiteral("[%1](%2)").arg(user, link));
+ }
+ }
- if (edit_.isEmpty()) {
- this->textBeforeEdit = input()->text();
- this->replyBeforeEdit = reply_;
- nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString());
- }
+ if (msgType == mtx::events::MessageType::Emote)
+ input()->setText("/me " + editText);
+ else
+ input()->setText(editText);
+ } else {
+ input()->setText("");
+ }
- if (edit_ != newEdit) {
- auto ev = events.get(newEdit.toStdString(), "");
- if (ev && mtx::accessors::sender(*ev) == http::client()->user_id().to_string()) {
- auto e = *ev;
- setReply(QString::fromStdString(
- mtx::accessors::relations(e).reply_to().value_or("")));
-
- auto msgType = mtx::accessors::msg_type(e);
- if (msgType == mtx::events::MessageType::Text ||
- msgType == mtx::events::MessageType::Notice ||
- msgType == mtx::events::MessageType::Emote) {
- auto relInfo = relatedInfo(newEdit);
- auto editText = relInfo.quoted_body;
-
- if (!relInfo.quoted_formatted_body.isEmpty()) {
- auto matches = conf::strings::matrixToLink.globalMatch(
- relInfo.quoted_formatted_body);
- std::map<QString, QString> reverseNameMapping;
- while (matches.hasNext()) {
- auto m = matches.next();
- reverseNameMapping[m.captured(2)] = m.captured(1);
- }
-
- for (const auto &[user, link] : reverseNameMapping) {
- // TODO(Nico): html unescape the user name
- editText.replace(
- user, QStringLiteral("[%1](%2)").arg(user, link));
- }
- }
-
- if (msgType == mtx::events::MessageType::Emote)
- input()->setText("/me " + editText);
- else
- input()->setText(editText);
- } else {
- input()->setText("");
- }
-
- edit_ = newEdit;
- } else {
- resetReply();
-
- input()->setText("");
- edit_ = "";
- }
- emit editChanged(edit_);
+ edit_ = newEdit;
+ } else {
+ resetReply();
+
+ input()->setText("");
+ edit_ = "";
}
+ emit editChanged(edit_);
+ }
}
void
TimelineModel::resetEdit()
{
- if (!edit_.isEmpty()) {
- edit_ = "";
- emit editChanged(edit_);
- nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString());
- input()->setText(textBeforeEdit);
- textBeforeEdit.clear();
- if (replyBeforeEdit.isEmpty())
- resetReply();
- else
- setReply(replyBeforeEdit);
- replyBeforeEdit.clear();
- }
+ if (!edit_.isEmpty()) {
+ edit_ = "";
+ emit editChanged(edit_);
+ nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString());
+ input()->setText(textBeforeEdit);
+ textBeforeEdit.clear();
+ if (replyBeforeEdit.isEmpty())
+ resetReply();
+ else
+ setReply(replyBeforeEdit);
+ replyBeforeEdit.clear();
+ }
}
QString
TimelineModel::roomName() const
{
- auto info = cache::getRoomInfo({room_id_.toStdString()});
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
- if (!info.count(room_id_))
- return "";
- else
- return utils::replaceEmoji(
- QString::fromStdString(info[room_id_].name).toHtmlEscaped());
+ if (!info.count(room_id_))
+ return "";
+ else
+ return utils::replaceEmoji(QString::fromStdString(info[room_id_].name).toHtmlEscaped());
}
QString
TimelineModel::plainRoomName() const
{
- auto info = cache::getRoomInfo({room_id_.toStdString()});
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
- if (!info.count(room_id_))
- return "";
- else
- return QString::fromStdString(info[room_id_].name);
+ if (!info.count(room_id_))
+ return "";
+ else
+ return QString::fromStdString(info[room_id_].name);
}
QString
TimelineModel::roomAvatarUrl() const
{
- auto info = cache::getRoomInfo({room_id_.toStdString()});
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
- if (!info.count(room_id_))
- return "";
- else
- return QString::fromStdString(info[room_id_].avatar_url);
+ if (!info.count(room_id_))
+ return "";
+ else
+ return QString::fromStdString(info[room_id_].avatar_url);
}
QString
TimelineModel::roomTopic() const
{
- auto info = cache::getRoomInfo({room_id_.toStdString()});
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
- if (!info.count(room_id_))
- return "";
- else
- return utils::replaceEmoji(utils::linkifyMessage(
- QString::fromStdString(info[room_id_].topic).toHtmlEscaped()));
+ if (!info.count(room_id_))
+ return "";
+ else
+ return utils::replaceEmoji(
+ utils::linkifyMessage(QString::fromStdString(info[room_id_].topic).toHtmlEscaped()));
}
crypto::Trust
TimelineModel::trustlevel() const
{
- if (!isEncrypted_)
- return crypto::Trust::Unverified;
+ if (!isEncrypted_)
+ return crypto::Trust::Unverified;
- return cache::client()->roomVerificationStatus(room_id_.toStdString());
+ return cache::client()->roomVerificationStatus(room_id_.toStdString());
}
int
TimelineModel::roomMemberCount() const
{
- return (int)cache::client()->memberCount(room_id_.toStdString());
+ return (int)cache::client()->memberCount(room_id_.toStdString());
}
QString
TimelineModel::directChatOtherUserId() const
{
- if (roomMemberCount() < 3) {
- QString id;
- for (auto member : cache::getMembers(room_id_.toStdString()))
- if (member.user_id != UserSettings::instance()->userId())
- id = member.user_id;
- return id;
- } else
- return "";
+ if (roomMemberCount() < 3) {
+ QString id;
+ for (auto member : cache::getMembers(room_id_.toStdString()))
+ if (member.user_id != UserSettings::instance()->userId())
+ id = member.user_id;
+ return id;
+ } else
+ return "";
}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 66e0622e..f16529e2 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -39,95 +39,95 @@ Q_NAMESPACE
enum EventType
{
- // Unsupported event
- Unsupported,
- /// m.room_key_request
- KeyRequest,
- /// m.reaction,
- Reaction,
- /// m.room.aliases
- Aliases,
- /// m.room.avatar
- Avatar,
- /// m.call.invite
- CallInvite,
- /// m.call.answer
- CallAnswer,
- /// m.call.hangup
- CallHangUp,
- /// m.call.candidates
- CallCandidates,
- /// m.room.canonical_alias
- CanonicalAlias,
- /// m.room.create
- RoomCreate,
- /// m.room.encrypted.
- Encrypted,
- /// m.room.encryption.
- Encryption,
- /// m.room.guest_access
- RoomGuestAccess,
- /// m.room.history_visibility
- RoomHistoryVisibility,
- /// m.room.join_rules
- RoomJoinRules,
- /// m.room.member
- Member,
- /// m.room.name
- Name,
- /// m.room.power_levels
- PowerLevels,
- /// m.room.tombstone
- Tombstone,
- /// m.room.topic
- Topic,
- /// m.room.redaction
- Redaction,
- /// m.room.pinned_events
- PinnedEvents,
- // m.sticker
- Sticker,
- // m.tag
- Tag,
- /// m.room.message
- AudioMessage,
- EmoteMessage,
- FileMessage,
- ImageMessage,
- LocationMessage,
- NoticeMessage,
- TextMessage,
- VideoMessage,
- Redacted,
- UnknownMessage,
- KeyVerificationRequest,
- KeyVerificationStart,
- KeyVerificationMac,
- KeyVerificationAccept,
- KeyVerificationCancel,
- KeyVerificationKey,
- KeyVerificationDone,
- KeyVerificationReady,
- //! m.image_pack, currently im.ponies.room_emotes
- ImagePackInRoom,
- //! m.image_pack, currently im.ponies.user_emotes
- ImagePackInAccountData,
- //! m.image_pack.rooms, currently im.ponies.emote_rooms
- ImagePackRooms,
+ // Unsupported event
+ Unsupported,
+ /// m.room_key_request
+ KeyRequest,
+ /// m.reaction,
+ Reaction,
+ /// m.room.aliases
+ Aliases,
+ /// m.room.avatar
+ Avatar,
+ /// m.call.invite
+ CallInvite,
+ /// m.call.answer
+ CallAnswer,
+ /// m.call.hangup
+ CallHangUp,
+ /// m.call.candidates
+ CallCandidates,
+ /// m.room.canonical_alias
+ CanonicalAlias,
+ /// m.room.create
+ RoomCreate,
+ /// m.room.encrypted.
+ Encrypted,
+ /// m.room.encryption.
+ Encryption,
+ /// m.room.guest_access
+ RoomGuestAccess,
+ /// m.room.history_visibility
+ RoomHistoryVisibility,
+ /// m.room.join_rules
+ RoomJoinRules,
+ /// m.room.member
+ Member,
+ /// m.room.name
+ Name,
+ /// m.room.power_levels
+ PowerLevels,
+ /// m.room.tombstone
+ Tombstone,
+ /// m.room.topic
+ Topic,
+ /// m.room.redaction
+ Redaction,
+ /// m.room.pinned_events
+ PinnedEvents,
+ // m.sticker
+ Sticker,
+ // m.tag
+ Tag,
+ /// m.room.message
+ AudioMessage,
+ EmoteMessage,
+ FileMessage,
+ ImageMessage,
+ LocationMessage,
+ NoticeMessage,
+ TextMessage,
+ VideoMessage,
+ Redacted,
+ UnknownMessage,
+ KeyVerificationRequest,
+ KeyVerificationStart,
+ KeyVerificationMac,
+ KeyVerificationAccept,
+ KeyVerificationCancel,
+ KeyVerificationKey,
+ KeyVerificationDone,
+ KeyVerificationReady,
+ //! m.image_pack, currently im.ponies.room_emotes
+ ImagePackInRoom,
+ //! m.image_pack, currently im.ponies.user_emotes
+ ImagePackInAccountData,
+ //! m.image_pack.rooms, currently im.ponies.emote_rooms
+ ImagePackRooms,
};
Q_ENUM_NS(EventType)
mtx::events::EventType fromRoomEventType(qml_mtx_events::EventType);
enum EventState
{
- //! The plaintext message was received by the server.
- Received,
- //! At least one of the participants has read the message.
- Read,
- //! The client sent the message. Not yet received.
- Sent,
- //! When the message is loaded from cache or backfill.
- Empty,
+ //! The plaintext message was received by the server.
+ Received,
+ //! At least one of the participants has read the message.
+ Read,
+ //! The client sent the message. Not yet received.
+ Sent,
+ //! When the message is loaded from cache or backfill.
+ Empty,
};
Q_ENUM_NS(EventState)
}
@@ -135,330 +135,329 @@ Q_ENUM_NS(EventState)
class StateKeeper
{
public:
- StateKeeper(std::function<void()> &&fn)
- : fn_(std::move(fn))
- {}
+ StateKeeper(std::function<void()> &&fn)
+ : fn_(std::move(fn))
+ {}
- ~StateKeeper() { fn_(); }
+ ~StateKeeper() { fn_(); }
private:
- std::function<void()> fn_;
+ std::function<void()> fn_;
};
struct DecryptionResult
{
- //! The decrypted content as a normal plaintext event.
- mtx::events::collections::TimelineEvents event;
- //! Whether or not the decryption was successful.
- bool isDecrypted = false;
+ //! The decrypted content as a normal plaintext event.
+ mtx::events::collections::TimelineEvents event;
+ //! Whether or not the decryption was successful.
+ bool isDecrypted = false;
};
class TimelineViewManager;
class TimelineModel : public QAbstractListModel
{
- Q_OBJECT
- Q_PROPERTY(
- int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
- Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
- typingUsersChanged)
- Q_PROPERTY(QString scrollTarget READ scrollTarget NOTIFY scrollTargetChanged)
- Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
- Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
- Q_PROPERTY(
- bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
- Q_PROPERTY(QString roomId READ roomId CONSTANT)
- Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
- Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged)
- Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
- Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
- Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
- Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
- Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
- Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
- Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged)
- Q_PROPERTY(QString directChatOtherUserId READ directChatOtherUserId NOTIFY
- directChatOtherUserIdChanged)
- Q_PROPERTY(InputBar *input READ input CONSTANT)
- Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
+ Q_OBJECT
+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+ Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
+ typingUsersChanged)
+ Q_PROPERTY(QString scrollTarget READ scrollTarget NOTIFY scrollTargetChanged)
+ Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
+ Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
+ Q_PROPERTY(
+ bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
+ Q_PROPERTY(QString roomId READ roomId CONSTANT)
+ Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
+ Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged)
+ Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
+ Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
+ Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
+ Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
+ Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
+ Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
+ Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged)
+ Q_PROPERTY(
+ QString directChatOtherUserId READ directChatOtherUserId NOTIFY directChatOtherUserIdChanged)
+ Q_PROPERTY(InputBar *input READ input CONSTANT)
+ Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
public:
- explicit TimelineModel(TimelineViewManager *manager,
- QString room_id,
- QObject *parent = nullptr);
-
- enum Roles
- {
- Type,
- TypeString,
- IsOnlyEmoji,
- Body,
- FormattedBody,
- PreviousMessageUserId,
- IsSender,
- UserId,
- UserName,
- PreviousMessageDay,
- Day,
- Timestamp,
- Url,
- ThumbnailUrl,
- Blurhash,
- Filename,
- Filesize,
- MimeType,
- OriginalHeight,
- OriginalWidth,
- ProportionalHeight,
- EventId,
- State,
- IsEdited,
- IsEditable,
- IsEncrypted,
- Trustlevel,
- EncryptionError,
- ReplyTo,
- Reactions,
- RoomId,
- RoomName,
- RoomTopic,
- CallType,
- Dump,
- RelatedEventCacheBuster,
- };
- Q_ENUM(Roles);
-
- QHash<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
- Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo);
-
- bool canFetchMore(const QModelIndex &) const override;
- void fetchMore(const QModelIndex &) override;
-
- Q_INVOKABLE QString displayName(QString id) const;
- Q_INVOKABLE QString avatarUrl(QString id) const;
- Q_INVOKABLE QString formatDateSeparator(QDate date) const;
- Q_INVOKABLE QString formatTypingUsers(const std::vector<QString> &users, QColor bg);
- Q_INVOKABLE bool showAcceptKnockButton(QString id);
- Q_INVOKABLE void acceptKnock(QString id);
- Q_INVOKABLE QString formatMemberEvent(QString id);
- Q_INVOKABLE QString formatJoinRuleEvent(QString id);
- Q_INVOKABLE QString formatHistoryVisibilityEvent(QString id);
- Q_INVOKABLE QString formatGuestAccessEvent(QString id);
- Q_INVOKABLE QString formatPowerLevelEvent(QString id);
-
- Q_INVOKABLE void viewRawMessage(QString id);
- Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
- Q_INVOKABLE void viewDecryptedRawMessage(QString id);
- Q_INVOKABLE void openUserProfile(QString userid);
- Q_INVOKABLE void editAction(QString id);
- Q_INVOKABLE void replyAction(QString id);
- Q_INVOKABLE void showReadReceipts(QString id);
- Q_INVOKABLE void redactEvent(QString id);
- Q_INVOKABLE int idToIndex(QString id) const;
- Q_INVOKABLE QString indexToId(int index) const;
- Q_INVOKABLE void openMedia(QString eventId);
- Q_INVOKABLE void cacheMedia(QString eventId);
- Q_INVOKABLE bool saveMedia(QString eventId) const;
- Q_INVOKABLE void showEvent(QString eventId);
- Q_INVOKABLE void copyLinkToEvent(QString eventId) const;
- void cacheMedia(QString eventId, std::function<void(const QString filename)> callback);
- Q_INVOKABLE void sendReset()
- {
- beginResetModel();
- endResetModel();
- }
-
- Q_INVOKABLE void requestKeyForEvent(QString id);
-
- std::vector<::Reaction> reactions(const std::string &event_id)
- {
- auto list = events.reactions(event_id);
- std::vector<::Reaction> vec;
- for (const auto &r : list)
- vec.push_back(r.value<Reaction>());
- return vec;
- }
-
- void updateLastMessage();
- void sync(const mtx::responses::JoinedRoom &room);
- void addEvents(const mtx::responses::Timeline &events);
- void syncState(const mtx::responses::State &state);
- template<class T>
- void sendMessageEvent(const T &content, mtx::events::EventType eventType);
- RelatedInfo relatedInfo(QString id);
-
- DescInfo lastMessage() const { return lastMessage_; }
- bool isSpace() const { return isSpace_; }
- bool isEncrypted() const { return isEncrypted_; }
- crypto::Trust trustlevel() const;
- int roomMemberCount() const;
- bool isDirect() const { return roomMemberCount() <= 2; }
- QString directChatOtherUserId() const;
-
- std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id)
- {
- auto e = events.get(id.toStdString(), "");
- if (e)
- return *e;
- else
- return std::nullopt;
- }
+ explicit TimelineModel(TimelineViewManager *manager,
+ QString room_id,
+ QObject *parent = nullptr);
+
+ enum Roles
+ {
+ Type,
+ TypeString,
+ IsOnlyEmoji,
+ Body,
+ FormattedBody,
+ PreviousMessageUserId,
+ IsSender,
+ UserId,
+ UserName,
+ PreviousMessageDay,
+ Day,
+ Timestamp,
+ Url,
+ ThumbnailUrl,
+ Blurhash,
+ Filename,
+ Filesize,
+ MimeType,
+ OriginalHeight,
+ OriginalWidth,
+ ProportionalHeight,
+ EventId,
+ State,
+ IsEdited,
+ IsEditable,
+ IsEncrypted,
+ Trustlevel,
+ EncryptionError,
+ ReplyTo,
+ Reactions,
+ RoomId,
+ RoomName,
+ RoomTopic,
+ CallType,
+ Dump,
+ RelatedEventCacheBuster,
+ };
+ Q_ENUM(Roles);
+
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
+ Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo);
+
+ bool canFetchMore(const QModelIndex &) const override;
+ void fetchMore(const QModelIndex &) override;
+
+ Q_INVOKABLE QString displayName(QString id) const;
+ Q_INVOKABLE QString avatarUrl(QString id) const;
+ Q_INVOKABLE QString formatDateSeparator(QDate date) const;
+ Q_INVOKABLE QString formatTypingUsers(const std::vector<QString> &users, QColor bg);
+ Q_INVOKABLE bool showAcceptKnockButton(QString id);
+ Q_INVOKABLE void acceptKnock(QString id);
+ Q_INVOKABLE QString formatMemberEvent(QString id);
+ Q_INVOKABLE QString formatJoinRuleEvent(QString id);
+ Q_INVOKABLE QString formatHistoryVisibilityEvent(QString id);
+ Q_INVOKABLE QString formatGuestAccessEvent(QString id);
+ Q_INVOKABLE QString formatPowerLevelEvent(QString id);
+
+ Q_INVOKABLE void viewRawMessage(QString id);
+ Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
+ Q_INVOKABLE void viewDecryptedRawMessage(QString id);
+ Q_INVOKABLE void openUserProfile(QString userid);
+ Q_INVOKABLE void editAction(QString id);
+ Q_INVOKABLE void replyAction(QString id);
+ Q_INVOKABLE void showReadReceipts(QString id);
+ Q_INVOKABLE void redactEvent(QString id);
+ Q_INVOKABLE int idToIndex(QString id) const;
+ Q_INVOKABLE QString indexToId(int index) const;
+ Q_INVOKABLE void openMedia(QString eventId);
+ Q_INVOKABLE void cacheMedia(QString eventId);
+ Q_INVOKABLE bool saveMedia(QString eventId) const;
+ Q_INVOKABLE void showEvent(QString eventId);
+ Q_INVOKABLE void copyLinkToEvent(QString eventId) const;
+ void cacheMedia(QString eventId, std::function<void(const QString filename)> callback);
+ Q_INVOKABLE void sendReset()
+ {
+ beginResetModel();
+ endResetModel();
+ }
+
+ Q_INVOKABLE void requestKeyForEvent(QString id);
+
+ std::vector<::Reaction> reactions(const std::string &event_id)
+ {
+ auto list = events.reactions(event_id);
+ std::vector<::Reaction> vec;
+ for (const auto &r : list)
+ vec.push_back(r.value<Reaction>());
+ return vec;
+ }
+
+ void updateLastMessage();
+ void sync(const mtx::responses::JoinedRoom &room);
+ void addEvents(const mtx::responses::Timeline &events);
+ void syncState(const mtx::responses::State &state);
+ template<class T>
+ void sendMessageEvent(const T &content, mtx::events::EventType eventType);
+ RelatedInfo relatedInfo(QString id);
+
+ DescInfo lastMessage() const { return lastMessage_; }
+ bool isSpace() const { return isSpace_; }
+ bool isEncrypted() const { return isEncrypted_; }
+ crypto::Trust trustlevel() const;
+ int roomMemberCount() const;
+ bool isDirect() const { return roomMemberCount() <= 2; }
+ QString directChatOtherUserId() const;
+
+ std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id)
+ {
+ auto e = events.get(id.toStdString(), "");
+ if (e)
+ return *e;
+ else
+ return std::nullopt;
+ }
public slots:
- void setCurrentIndex(int index);
- int currentIndex() const { return idToIndex(currentId); }
- void eventShown();
- void markEventsAsRead(const std::vector<QString> &event_ids);
- QVariantMap getDump(QString eventId, QString relatedTo) const;
- void updateTypingUsers(const std::vector<QString> &users)
- {
- if (this->typingUsers_ != users) {
- this->typingUsers_ = users;
- emit typingUsersChanged(typingUsers_);
- }
+ void setCurrentIndex(int index);
+ int currentIndex() const { return idToIndex(currentId); }
+ void eventShown();
+ void markEventsAsRead(const std::vector<QString> &event_ids);
+ QVariantMap getDump(QString eventId, QString relatedTo) const;
+ void updateTypingUsers(const std::vector<QString> &users)
+ {
+ if (this->typingUsers_ != users) {
+ this->typingUsers_ = users;
+ emit typingUsersChanged(typingUsers_);
}
- std::vector<QString> typingUsers() const { return typingUsers_; }
- bool paginationInProgress() const { return m_paginationInProgress; }
- QString reply() const { return reply_; }
- void setReply(QString newReply)
- {
- if (edit_.startsWith('m'))
- return;
-
- if (reply_ != newReply) {
- reply_ = newReply;
- emit replyChanged(reply_);
- }
+ }
+ std::vector<QString> typingUsers() const { return typingUsers_; }
+ bool paginationInProgress() const { return m_paginationInProgress; }
+ QString reply() const { return reply_; }
+ void setReply(QString newReply)
+ {
+ if (edit_.startsWith('m'))
+ return;
+
+ if (reply_ != newReply) {
+ reply_ = newReply;
+ emit replyChanged(reply_);
}
- void resetReply()
- {
- if (!reply_.isEmpty()) {
- reply_ = "";
- emit replyChanged(reply_);
- }
+ }
+ void resetReply()
+ {
+ if (!reply_.isEmpty()) {
+ reply_ = "";
+ emit replyChanged(reply_);
}
- QString edit() const { return edit_; }
- void setEdit(QString newEdit);
- void resetEdit();
- void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; }
- void clearTimeline() { events.clearTimeline(); }
- void receivedSessionKey(const std::string &session_key)
- {
- events.receivedSessionKey(session_key);
- }
-
- QString roomName() const;
- QString plainRoomName() const;
- QString roomTopic() const;
- InputBar *input() { return &input_; }
- Permissions *permissions() { return &permissions_; }
- QString roomAvatarUrl() const;
- QString roomId() const { return room_id_; }
-
- bool hasMentions() { return highlight_count > 0; }
- int notificationCount() { return notification_count; }
-
- QString scrollTarget() const;
+ }
+ QString edit() const { return edit_; }
+ void setEdit(QString newEdit);
+ void resetEdit();
+ void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; }
+ void clearTimeline() { events.clearTimeline(); }
+ void receivedSessionKey(const std::string &session_key)
+ {
+ events.receivedSessionKey(session_key);
+ }
+
+ QString roomName() const;
+ QString plainRoomName() const;
+ QString roomTopic() const;
+ InputBar *input() { return &input_; }
+ Permissions *permissions() { return &permissions_; }
+ QString roomAvatarUrl() const;
+ QString roomId() const { return room_id_; }
+
+ bool hasMentions() { return highlight_count > 0; }
+ int notificationCount() { return notification_count; }
+
+ QString scrollTarget() const;
private slots:
- void addPendingMessage(mtx::events::collections::TimelineEvents event);
- void scrollTimerEvent();
+ void addPendingMessage(mtx::events::collections::TimelineEvents event);
+ void scrollTimerEvent();
signals:
- void currentIndexChanged(int index);
- void redactionFailed(QString id);
- void eventRedacted(QString id);
- void mediaCached(QString mxcUrl, QString cacheUrl);
- void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
- void typingUsersChanged(std::vector<QString> users);
- void replyChanged(QString reply);
- void editChanged(QString reply);
- void openReadReceiptsDialog(ReadReceiptsProxy *rr);
- void showRawMessageDialog(QString rawMessage);
- void paginationInProgressChanged(const bool);
- void newCallEvent(const mtx::events::collections::TimelineEvents &event);
- void scrollToIndex(int index);
-
- void lastMessageChanged();
- void notificationsChanged();
-
- void newMessageToSend(mtx::events::collections::TimelineEvents event);
- void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
- void updateFlowEventId(std::string event_id);
-
- void encryptionChanged();
- void trustlevelChanged();
- void roomNameChanged();
- void plainRoomNameChanged();
- void roomTopicChanged();
- void roomAvatarUrlChanged();
- void roomMemberCountChanged();
- void isDirectChanged();
- void directChatOtherUserIdChanged();
- void permissionsChanged();
- void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
-
- void scrollTargetChanged();
+ void currentIndexChanged(int index);
+ void redactionFailed(QString id);
+ void eventRedacted(QString id);
+ void mediaCached(QString mxcUrl, QString cacheUrl);
+ void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
+ void typingUsersChanged(std::vector<QString> users);
+ void replyChanged(QString reply);
+ void editChanged(QString reply);
+ void openReadReceiptsDialog(ReadReceiptsProxy *rr);
+ void showRawMessageDialog(QString rawMessage);
+ void paginationInProgressChanged(const bool);
+ void newCallEvent(const mtx::events::collections::TimelineEvents &event);
+ void scrollToIndex(int index);
+
+ void lastMessageChanged();
+ void notificationsChanged();
+
+ void newMessageToSend(mtx::events::collections::TimelineEvents event);
+ void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
+ void updateFlowEventId(std::string event_id);
+
+ void encryptionChanged();
+ void trustlevelChanged();
+ void roomNameChanged();
+ void plainRoomNameChanged();
+ void roomTopicChanged();
+ void roomAvatarUrlChanged();
+ void roomMemberCountChanged();
+ void isDirectChanged();
+ void directChatOtherUserIdChanged();
+ void permissionsChanged();
+ void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
+
+ void scrollTargetChanged();
private:
- template<typename T>
- void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType);
- void readEvent(const std::string &id);
+ template<typename T>
+ void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType);
+ void readEvent(const std::string &id);
- void setPaginationInProgress(const bool paginationInProgress);
+ void setPaginationInProgress(const bool paginationInProgress);
- QSet<QString> read;
+ QSet<QString> read;
- mutable EventStore events;
+ mutable EventStore events;
- QString room_id_;
+ QString room_id_;
- QString currentId, currentReadId;
- QString reply_, edit_;
- QString textBeforeEdit, replyBeforeEdit;
- std::vector<QString> typingUsers_;
+ QString currentId, currentReadId;
+ QString reply_, edit_;
+ QString textBeforeEdit, replyBeforeEdit;
+ std::vector<QString> typingUsers_;
- TimelineViewManager *manager_;
+ TimelineViewManager *manager_;
- InputBar input_{this};
- Permissions permissions_;
+ InputBar input_{this};
+ Permissions permissions_;
- QTimer showEventTimer{this};
- QString eventIdToShow;
- int showEventTimerCounter = 0;
+ QTimer showEventTimer{this};
+ QString eventIdToShow;
+ int showEventTimerCounter = 0;
- DescInfo lastMessage_{};
+ DescInfo lastMessage_{};
- friend struct SendMessageVisitor;
+ friend struct SendMessageVisitor;
- int notification_count = 0, highlight_count = 0;
+ int notification_count = 0, highlight_count = 0;
- unsigned int relatedEventCacheBuster = 0;
+ unsigned int relatedEventCacheBuster = 0;
- bool decryptDescription = true;
- bool m_paginationInProgress = false;
- bool isSpace_ = false;
- bool isEncrypted_ = false;
+ bool decryptDescription = true;
+ bool m_paginationInProgress = false;
+ bool isSpace_ = false;
+ bool isEncrypted_ = false;
};
template<class T>
void
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
{
- if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) {
- mtx::events::Sticker msgCopy = {};
- msgCopy.content = content;
- msgCopy.type = eventType;
- emit newMessageToSend(msgCopy);
- } else {
- mtx::events::RoomEvent<T> msgCopy = {};
- msgCopy.content = content;
- msgCopy.type = eventType;
- emit newMessageToSend(msgCopy);
- }
- resetReply();
- resetEdit();
+ if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) {
+ mtx::events::Sticker msgCopy = {};
+ msgCopy.content = content;
+ msgCopy.type = eventType;
+ emit newMessageToSend(msgCopy);
+ } else {
+ mtx::events::RoomEvent<T> msgCopy = {};
+ msgCopy.content = content;
+ msgCopy.type = eventType;
+ emit newMessageToSend(msgCopy);
+ }
+ resetReply();
+ resetEdit();
}
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index ea231b03..8a33dc2b 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -67,73 +67,72 @@ template<typename T>
static constexpr bool
messageWithFileAndUrl(const mtx::events::Event<T> &)
{
- return is_detected<file_t, T>::value && is_detected<url_t, T>::value;
+ return is_detected<file_t, T>::value && is_detected<url_t, T>::value;
}
template<typename T>
static constexpr void
removeReplyFallback(mtx::events::Event<T> &e)
{
- if constexpr (is_detected<body_t, T>::value) {
- if constexpr (std::is_same_v<std::optional<std::string>,
- std::remove_cv_t<decltype(e.content.body)>>) {
- if (e.content.body) {
- e.content.body = utils::stripReplyFromBody(e.content.body);
- }
- } else if constexpr (std::is_same_v<std::string,
- std::remove_cv_t<decltype(e.content.body)>>) {
- e.content.body = utils::stripReplyFromBody(e.content.body);
- }
+ if constexpr (is_detected<body_t, T>::value) {
+ if constexpr (std::is_same_v<std::optional<std::string>,
+ std::remove_cv_t<decltype(e.content.body)>>) {
+ if (e.content.body) {
+ e.content.body = utils::stripReplyFromBody(e.content.body);
+ }
+ } else if constexpr (std::is_same_v<std::string,
+ std::remove_cv_t<decltype(e.content.body)>>) {
+ e.content.body = utils::stripReplyFromBody(e.content.body);
}
+ }
- if constexpr (is_detected<formatted_body_t, T>::value) {
- if (e.content.format == "org.matrix.custom.html") {
- e.content.formatted_body =
- utils::stripReplyFromFormattedBody(e.content.formatted_body);
- }
+ if constexpr (is_detected<formatted_body_t, T>::value) {
+ if (e.content.format == "org.matrix.custom.html") {
+ e.content.formatted_body = utils::stripReplyFromFormattedBody(e.content.formatted_body);
}
+ }
}
}
void
TimelineViewManager::updateColorPalette()
{
- userColors.clear();
+ userColors.clear();
- if (ChatPage::instance()->userSettings()->theme() == "light") {
- view->rootContext()->setContextProperty("currentActivePalette", QPalette());
- view->rootContext()->setContextProperty("currentInactivePalette", QPalette());
- } else if (ChatPage::instance()->userSettings()->theme() == "dark") {
- view->rootContext()->setContextProperty("currentActivePalette", QPalette());
- view->rootContext()->setContextProperty("currentInactivePalette", QPalette());
- } else {
- view->rootContext()->setContextProperty("currentActivePalette", QPalette());
- view->rootContext()->setContextProperty("currentInactivePalette", nullptr);
- }
+ if (ChatPage::instance()->userSettings()->theme() == "light") {
+ view->rootContext()->setContextProperty("currentActivePalette", QPalette());
+ view->rootContext()->setContextProperty("currentInactivePalette", QPalette());
+ } else if (ChatPage::instance()->userSettings()->theme() == "dark") {
+ view->rootContext()->setContextProperty("currentActivePalette", QPalette());
+ view->rootContext()->setContextProperty("currentInactivePalette", QPalette());
+ } else {
+ view->rootContext()->setContextProperty("currentActivePalette", QPalette());
+ view->rootContext()->setContextProperty("currentInactivePalette", nullptr);
+ }
}
QColor
TimelineViewManager::userColor(QString id, QColor background)
{
- if (!userColors.contains(id))
- userColors.insert(id, QColor(utils::generateContrastingHexColor(id, background)));
- return userColors.value(id);
+ if (!userColors.contains(id))
+ userColors.insert(id, QColor(utils::generateContrastingHexColor(id, background)));
+ return userColors.value(id);
}
QString
TimelineViewManager::userPresence(QString id) const
{
- if (id.isEmpty())
- return "";
- else
- return QString::fromStdString(
- mtx::presence::to_string(cache::presenceState(id.toStdString())));
+ if (id.isEmpty())
+ return "";
+ else
+ return QString::fromStdString(
+ mtx::presence::to_string(cache::presenceState(id.toStdString())));
}
QString
TimelineViewManager::userStatus(QString id) const
{
- return QString::fromStdString(cache::statusMessage(id.toStdString()));
+ return QString::fromStdString(cache::statusMessage(id.toStdString()));
}
TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
@@ -146,453 +145,432 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
, rooms_(new RoomlistModel(this))
, communities_(new CommunitiesModel(this))
{
- qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationDone>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationKey>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationMac>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
- qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
- qRegisterMetaType<CombinedImagePackModel *>();
-
- qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
-
- qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
- "im.nheko",
- 1,
- 0,
- "MtxEvent",
- "Can't instantiate enum!");
- qmlRegisterUncreatableMetaObject(
- olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!");
- qmlRegisterUncreatableMetaObject(
- crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
- qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
- "im.nheko",
- 1,
- 0,
- "VerificationStatus",
- "Can't instantiate enum!");
-
- qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
- qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
- qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
- qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
- qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
- qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
- qmlRegisterUncreatableType<DeviceVerificationFlow>(
- "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!");
- qmlRegisterUncreatableType<UserProfile>(
- "im.nheko",
- 1,
- 0,
- "UserProfileModel",
- "UserProfile needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<MemberList>(
- "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<RoomSettings>(
- "im.nheko",
- 1,
- 0,
- "RoomSettingsModel",
- "Room Settings needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<TimelineModel>(
- "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<ImagePackListModel>(
- "im.nheko",
- 1,
- 0,
- "ImagePackListModel",
- "ImagePackListModel needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<SingleImagePackModel>(
- "im.nheko",
- 1,
- 0,
- "SingleImagePackModel",
- "SingleImagePackModel needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<InviteesModel>(
- "im.nheko",
- 1,
- 0,
- "InviteesModel",
- "InviteesModel needs to be instantiated on the C++ side");
- qmlRegisterUncreatableType<ReadReceiptsProxy>(
- "im.nheko",
- 1,
- 0,
- "ReadReceiptsProxy",
- "ReadReceiptsProxy needs to be instantiated on the C++ side");
-
- static auto self = this;
- qmlRegisterSingletonType<MainWindow>(
- "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = MainWindow::instance();
- QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
- return ptr;
- });
- qmlRegisterSingletonType<TimelineViewManager>(
- "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = self;
- QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
- return ptr;
- });
- qmlRegisterSingletonType<RoomlistModel>(
- "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = new FilteredRoomlistModel(self->rooms_);
-
- connect(self->communities_,
- &CommunitiesModel::currentTagIdChanged,
- ptr,
- &FilteredRoomlistModel::updateFilterTag);
- connect(self->communities_,
- &CommunitiesModel::hiddenTagsChanged,
- ptr,
- &FilteredRoomlistModel::updateHiddenTagsAndSpaces);
- return ptr;
- });
- qmlRegisterSingletonType<RoomlistModel>(
- "im.nheko", 1, 0, "Communities", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = self->communities_;
- QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
- return ptr;
- });
- qmlRegisterSingletonType<UserSettings>(
- "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = ChatPage::instance()->userSettings().data();
- QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
- return ptr;
- });
- qmlRegisterSingletonType<CallManager>(
- "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * {
- auto ptr = ChatPage::instance()->callManager();
- QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
- return ptr;
- });
- qmlRegisterSingletonType<Clipboard>(
- "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
- return new Clipboard();
- });
- qmlRegisterSingletonType<Nheko>(
- "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
- return new Nheko();
- });
-
- qRegisterMetaType<mtx::events::collections::TimelineEvents>();
- qRegisterMetaType<std::vector<DeviceInfo>>();
-
- qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
- qmlRegisterUncreatableType<emoji::Emoji>(
- "im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models");
- qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
- "im.nheko.EmojiModel",
- 1,
- 0,
- "EmojiCategory",
- "Error: Only enums");
-
- qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
+ qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationDone>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationKey>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationMac>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
+ qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
+ qRegisterMetaType<CombinedImagePackModel *>();
+
+ qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
+
+ qmlRegisterUncreatableMetaObject(
+ qml_mtx_events::staticMetaObject, "im.nheko", 1, 0, "MtxEvent", "Can't instantiate enum!");
+ qmlRegisterUncreatableMetaObject(
+ olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!");
+ qmlRegisterUncreatableMetaObject(
+ crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
+ qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
+ "im.nheko",
+ 1,
+ 0,
+ "VerificationStatus",
+ "Can't instantiate enum!");
+
+ qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
+ qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
+ qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
+ qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
+ qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
+ qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
+ qmlRegisterUncreatableType<DeviceVerificationFlow>(
+ "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!");
+ qmlRegisterUncreatableType<UserProfile>(
+ "im.nheko", 1, 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<MemberList>(
+ "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<RoomSettings>(
+ "im.nheko",
+ 1,
+ 0,
+ "RoomSettingsModel",
+ "Room Settings needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<TimelineModel>(
+ "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<ImagePackListModel>(
+ "im.nheko",
+ 1,
+ 0,
+ "ImagePackListModel",
+ "ImagePackListModel needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<SingleImagePackModel>(
+ "im.nheko",
+ 1,
+ 0,
+ "SingleImagePackModel",
+ "SingleImagePackModel needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<InviteesModel>(
+ "im.nheko", 1, 0, "InviteesModel", "InviteesModel needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<ReadReceiptsProxy>(
+ "im.nheko",
+ 1,
+ 0,
+ "ReadReceiptsProxy",
+ "ReadReceiptsProxy needs to be instantiated on the C++ side");
+
+ static auto self = this;
+ qmlRegisterSingletonType<MainWindow>(
+ "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = MainWindow::instance();
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
+ return ptr;
+ });
+ qmlRegisterSingletonType<TimelineViewManager>(
+ "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = self;
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
+ return ptr;
+ });
+ qmlRegisterSingletonType<RoomlistModel>(
+ "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = new FilteredRoomlistModel(self->rooms_);
+
+ connect(self->communities_,
+ &CommunitiesModel::currentTagIdChanged,
+ ptr,
+ &FilteredRoomlistModel::updateFilterTag);
+ connect(self->communities_,
+ &CommunitiesModel::hiddenTagsChanged,
+ ptr,
+ &FilteredRoomlistModel::updateHiddenTagsAndSpaces);
+ return ptr;
+ });
+ qmlRegisterSingletonType<RoomlistModel>(
+ "im.nheko", 1, 0, "Communities", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = self->communities_;
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
+ return ptr;
+ });
+ qmlRegisterSingletonType<UserSettings>(
+ "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = ChatPage::instance()->userSettings().data();
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
+ return ptr;
+ });
+ qmlRegisterSingletonType<CallManager>(
+ "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ auto ptr = ChatPage::instance()->callManager();
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership);
+ return ptr;
+ });
+ qmlRegisterSingletonType<Clipboard>(
+ "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ return new Clipboard();
+ });
+ qmlRegisterSingletonType<Nheko>(
+ "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ return new Nheko();
+ });
+
+ qRegisterMetaType<mtx::events::collections::TimelineEvents>();
+ qRegisterMetaType<std::vector<DeviceInfo>>();
+
+ qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
+ qmlRegisterUncreatableType<emoji::Emoji>(
+ "im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models");
+ qmlRegisterUncreatableMetaObject(
+ emoji::staticMetaObject, "im.nheko.EmojiModel", 1, 0, "EmojiCategory", "Error: Only enums");
+
+ qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
#ifdef USE_QUICK_VIEW
- view = new QQuickView(parent);
- container = QWidget::createWindowContainer(view, parent);
+ view = new QQuickView(parent);
+ container = QWidget::createWindowContainer(view, parent);
#else
- view = new QQuickWidget(parent);
- container = view;
- view->setResizeMode(QQuickWidget::SizeRootObjectToView);
- container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
- connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
- nhlog::ui()->debug("Status changed to {}", status);
- });
+ view = new QQuickWidget(parent);
+ container = view;
+ view->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
+ nhlog::ui()->debug("Status changed to {}", status);
+ });
#endif
- container->setMinimumSize(200, 200);
- updateColorPalette();
- view->engine()->addImageProvider("MxcImage", imgProvider);
- view->engine()->addImageProvider("colorimage", colorImgProvider);
- view->engine()->addImageProvider("blurhash", blurhashProvider);
- if (JdenticonProvider::isAvailable())
- view->engine()->addImageProvider("jdenticon", jdenticonProvider);
- view->setSource(QUrl("qrc:///qml/Root.qml"));
-
- connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
- connect(
- dynamic_cast<ChatPage *>(parent),
- &ChatPage::receivedRoomDeviceVerificationRequest,
- this,
- [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
- TimelineModel *model) {
- if (this->isInitialSync_)
- return;
-
- auto event_id = QString::fromStdString(message.event_id);
- if (!this->dvList.contains(event_id)) {
- if (auto flow = DeviceVerificationFlow::NewInRoomVerification(
- this,
- model,
- message.content,
- QString::fromStdString(message.sender),
- event_id)) {
- dvList[event_id] = flow;
- emit newDeviceVerificationRequest(flow.data());
- }
- }
- });
- connect(dynamic_cast<ChatPage *>(parent),
- &ChatPage::receivedDeviceVerificationRequest,
- this,
- [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) {
- if (this->isInitialSync_)
- return;
-
- if (!msg.transaction_id)
- return;
-
- auto txnid = QString::fromStdString(msg.transaction_id.value());
- if (!this->dvList.contains(txnid)) {
- if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
- this, msg, QString::fromStdString(sender), txnid)) {
- dvList[txnid] = flow;
- emit newDeviceVerificationRequest(flow.data());
- }
- }
- });
- connect(dynamic_cast<ChatPage *>(parent),
- &ChatPage::receivedDeviceVerificationStart,
- this,
- [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) {
- if (this->isInitialSync_)
- return;
-
- if (!msg.transaction_id)
- return;
-
- auto txnid = QString::fromStdString(msg.transaction_id.value());
- if (!this->dvList.contains(txnid)) {
- if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
- this, msg, QString::fromStdString(sender), txnid)) {
- dvList[txnid] = flow;
- emit newDeviceVerificationRequest(flow.data());
- }
- }
- });
- connect(parent, &ChatPage::loggedOut, this, [this]() {
- isInitialSync_ = true;
- emit initialSyncChanged(true);
- });
+ container->setMinimumSize(200, 200);
+ updateColorPalette();
+ view->engine()->addImageProvider("MxcImage", imgProvider);
+ view->engine()->addImageProvider("colorimage", colorImgProvider);
+ view->engine()->addImageProvider("blurhash", blurhashProvider);
+ if (JdenticonProvider::isAvailable())
+ view->engine()->addImageProvider("jdenticon", jdenticonProvider);
+ view->setSource(QUrl("qrc:///qml/Root.qml"));
+
+ connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
+ connect(dynamic_cast<ChatPage *>(parent),
+ &ChatPage::receivedRoomDeviceVerificationRequest,
+ this,
+ [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
+ TimelineModel *model) {
+ if (this->isInitialSync_)
+ return;
+
+ auto event_id = QString::fromStdString(message.event_id);
+ if (!this->dvList.contains(event_id)) {
+ if (auto flow = DeviceVerificationFlow::NewInRoomVerification(
+ this,
+ model,
+ message.content,
+ QString::fromStdString(message.sender),
+ event_id)) {
+ dvList[event_id] = flow;
+ emit newDeviceVerificationRequest(flow.data());
+ }
+ }
+ });
+ connect(dynamic_cast<ChatPage *>(parent),
+ &ChatPage::receivedDeviceVerificationRequest,
+ this,
+ [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) {
+ if (this->isInitialSync_)
+ return;
+
+ if (!msg.transaction_id)
+ return;
+
+ auto txnid = QString::fromStdString(msg.transaction_id.value());
+ if (!this->dvList.contains(txnid)) {
+ if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
+ this, msg, QString::fromStdString(sender), txnid)) {
+ dvList[txnid] = flow;
+ emit newDeviceVerificationRequest(flow.data());
+ }
+ }
+ });
+ connect(dynamic_cast<ChatPage *>(parent),
+ &ChatPage::receivedDeviceVerificationStart,
+ this,
+ [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) {
+ if (this->isInitialSync_)
+ return;
+
+ if (!msg.transaction_id)
+ return;
+
+ auto txnid = QString::fromStdString(msg.transaction_id.value());
+ if (!this->dvList.contains(txnid)) {
+ if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
+ this, msg, QString::fromStdString(sender), txnid)) {
+ dvList[txnid] = flow;
+ emit newDeviceVerificationRequest(flow.data());
+ }
+ }
+ });
+ connect(parent, &ChatPage::loggedOut, this, [this]() {
+ isInitialSync_ = true;
+ emit initialSyncChanged(true);
+ });
- connect(this,
- &TimelineViewManager::openImageOverlayInternalCb,
- this,
- &TimelineViewManager::openImageOverlayInternal);
+ connect(this,
+ &TimelineViewManager::openImageOverlayInternalCb,
+ this,
+ &TimelineViewManager::openImageOverlayInternal);
}
void
TimelineViewManager::openRoomMembers(TimelineModel *room)
{
- if (!room)
- return;
- MemberList *memberList = new MemberList(room->roomId(), this);
- emit openRoomMembersDialog(memberList, room);
+ if (!room)
+ return;
+ MemberList *memberList = new MemberList(room->roomId(), this);
+ emit openRoomMembersDialog(memberList, room);
}
void
TimelineViewManager::openRoomSettings(QString room_id)
{
- RoomSettings *settings = new RoomSettings(room_id, this);
- connect(rooms_->getRoomById(room_id).data(),
- &TimelineModel::roomAvatarUrlChanged,
- settings,
- &RoomSettings::avatarChanged);
- emit openRoomSettingsDialog(settings);
+ RoomSettings *settings = new RoomSettings(room_id, this);
+ connect(rooms_->getRoomById(room_id).data(),
+ &TimelineModel::roomAvatarUrlChanged,
+ settings,
+ &RoomSettings::avatarChanged);
+ emit openRoomSettingsDialog(settings);
}
void
TimelineViewManager::openInviteUsers(QString roomId)
{
- InviteesModel *model = new InviteesModel{this};
- connect(model, &InviteesModel::accept, this, [this, model, roomId]() {
- emit inviteUsers(roomId, model->mxids());
- });
- emit openInviteUsersDialog(model);
+ InviteesModel *model = new InviteesModel{this};
+ connect(model, &InviteesModel::accept, this, [this, model, roomId]() {
+ emit inviteUsers(roomId, model->mxids());
+ });
+ emit openInviteUsersDialog(model);
}
void
TimelineViewManager::openGlobalUserProfile(QString userId)
{
- UserProfile *profile = new UserProfile{QString{}, userId, this};
- emit openProfile(profile);
+ UserProfile *profile = new UserProfile{QString{}, userId, this};
+ emit openProfile(profile);
}
void
TimelineViewManager::setVideoCallItem()
{
- WebRTCSession::instance().setVideoItem(
- view->rootObject()->findChild<QQuickItem *>("videoCallItem"));
+ WebRTCSession::instance().setVideoItem(
+ view->rootObject()->findChild<QQuickItem *>("videoCallItem"));
}
void
TimelineViewManager::sync(const mtx::responses::Rooms &rooms_res)
{
- this->rooms_->sync(rooms_res);
- this->communities_->sync(rooms_res);
+ this->rooms_->sync(rooms_res);
+ this->communities_->sync(rooms_res);
- if (isInitialSync_) {
- this->isInitialSync_ = false;
- emit initialSyncChanged(false);
- }
+ if (isInitialSync_) {
+ this->isInitialSync_ = false;
+ emit initialSyncChanged(false);
+ }
}
void
TimelineViewManager::showEvent(const QString &room_id, const QString &event_id)
{
- if (auto room = rooms_->getRoomById(room_id)) {
- if (rooms_->currentRoom() != room) {
- rooms_->setCurrentRoom(room_id);
- container->setFocus();
- nhlog::ui()->info("Activated room {}", room_id.toStdString());
- }
-
- room->showEvent(event_id);
+ if (auto room = rooms_->getRoomById(room_id)) {
+ if (rooms_->currentRoom() != room) {
+ rooms_->setCurrentRoom(room_id);
+ container->setFocus();
+ nhlog::ui()->info("Activated room {}", room_id.toStdString());
}
+
+ room->showEvent(event_id);
+ }
}
QString
TimelineViewManager::escapeEmoji(QString str) const
{
- return utils::replaceEmoji(str);
+ return utils::replaceEmoji(str);
}
void
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId)
{
- if (mxcUrl.isEmpty()) {
- return;
- }
+ if (mxcUrl.isEmpty()) {
+ return;
+ }
- MxcImageProvider::download(
- mxcUrl.remove("mxc://"), QSize(), [this, eventId](QString, QSize, QImage img, QString) {
- if (img.isNull()) {
- nhlog::ui()->error("Error when retrieving image for overlay.");
- return;
- }
+ MxcImageProvider::download(
+ mxcUrl.remove("mxc://"), QSize(), [this, eventId](QString, QSize, QImage img, QString) {
+ if (img.isNull()) {
+ nhlog::ui()->error("Error when retrieving image for overlay.");
+ return;
+ }
- emit openImageOverlayInternalCb(eventId, std::move(img));
- });
+ emit openImageOverlayInternalCb(eventId, std::move(img));
+ });
}
void
TimelineViewManager::openImagePackSettings(QString roomid)
{
- emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this));
+ emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this));
}
void
TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
{
- auto pixmap = QPixmap::fromImage(img);
+ auto pixmap = QPixmap::fromImage(img);
- auto imgDialog = new dialogs::ImageOverlay(pixmap);
- imgDialog->showFullScreen();
+ auto imgDialog = new dialogs::ImageOverlay(pixmap);
+ imgDialog->showFullScreen();
- auto room = rooms_->currentRoom();
- connect(imgDialog, &dialogs::ImageOverlay::saving, room, [eventId, imgDialog, room]() {
- // hide the overlay while presenting the save dialog for better
- // cross platform support.
- imgDialog->hide();
+ auto room = rooms_->currentRoom();
+ connect(imgDialog, &dialogs::ImageOverlay::saving, room, [eventId, imgDialog, room]() {
+ // hide the overlay while presenting the save dialog for better
+ // cross platform support.
+ imgDialog->hide();
- if (!room->saveMedia(eventId)) {
- imgDialog->show();
- } else {
- imgDialog->close();
- }
- });
+ if (!room->saveMedia(eventId)) {
+ imgDialog->show();
+ } else {
+ imgDialog->close();
+ }
+ });
}
void
TimelineViewManager::openLeaveRoomDialog(QString roomid) const
{
- MainWindow::instance()->openLeaveRoomDialog(roomid);
+ MainWindow::instance()->openLeaveRoomDialog(roomid);
}
void
TimelineViewManager::verifyUser(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) &&
- cache::isRoomEncrypted(room_id)) {
- auto room_members = cache::roomMembers(room_id);
- if (std::find(room_members.begin(),
- room_members.end(),
- (userid).toStdString()) != room_members.end()) {
- if (auto model =
- rooms_->getRoomById(QString::fromStdString(room_id))) {
- auto flow =
- DeviceVerificationFlow::InitiateUserVerification(
- this, model.data(), userid);
- connect(model.data(),
- &TimelineModel::updateFlowEventId,
- this,
- [this, flow](std::string eventId) {
- dvList[QString::fromStdString(eventId)] =
- flow;
- });
- emit newDeviceVerificationRequest(flow.data());
- return;
- }
- }
+ 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) &&
+ cache::isRoomEncrypted(room_id)) {
+ auto room_members = cache::roomMembers(room_id);
+ if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) !=
+ room_members.end()) {
+ if (auto model = rooms_->getRoomById(QString::fromStdString(room_id))) {
+ auto flow =
+ DeviceVerificationFlow::InitiateUserVerification(this, model.data(), userid);
+ connect(model.data(),
+ &TimelineModel::updateFlowEventId,
+ this,
+ [this, flow](std::string eventId) {
+ dvList[QString::fromStdString(eventId)] = flow;
+ });
+ emit newDeviceVerificationRequest(flow.data());
+ return;
}
+ }
}
+ }
- emit ChatPage::instance()->showNotification(
- tr("No encrypted private chat found with this user. Create an "
- "encrypted private chat with this user and try again."));
+ emit ChatPage::instance()->showNotification(
+ tr("No encrypted private chat found with this user. Create an "
+ "encrypted private chat with this user and try again."));
}
void
TimelineViewManager::removeVerificationFlow(DeviceVerificationFlow *flow)
{
- for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) {
- if ((*it).second == flow) {
- dvList.remove((*it).first);
- return;
- }
+ for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) {
+ if ((*it).second == flow) {
+ dvList.remove((*it).first);
+ return;
}
+ }
}
void
TimelineViewManager::verifyDevice(QString userid, QString deviceid)
{
- auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid);
- this->dvList[flow->transactionId()] = flow;
- emit newDeviceVerificationRequest(flow.data());
+ auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid);
+ this->dvList[flow->transactionId()] = flow;
+ emit newDeviceVerificationRequest(flow.data());
}
void
TimelineViewManager::updateReadReceipts(const QString &room_id,
const std::vector<QString> &event_ids)
{
- if (auto room = rooms_->getRoomById(room_id)) {
- room->markEventsAsRead(event_ids);
- }
+ if (auto room = rooms_->getRoomById(room_id)) {
+ room->markEventsAsRead(event_ids);
+ }
}
void
TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::string &session_id)
{
- if (auto room = rooms_->getRoomById(QString::fromStdString(room_id))) {
- room->receivedSessionKey(session_id);
- }
+ if (auto room = rooms_->getRoomById(QString::fromStdString(room_id))) {
+ room->receivedSessionKey(session_id);
+ }
}
void
TimelineViewManager::initializeRoomlist()
{
- rooms_->initializeRooms();
- communities_->initializeSidebar();
+ rooms_->initializeRooms();
+ communities_->initializeSidebar();
}
void
@@ -600,178 +578,175 @@ TimelineViewManager::queueReply(const QString &roomid,
const QString &repliedToEvent,
const QString &replyBody)
{
- if (auto room = rooms_->getRoomById(roomid)) {
- room->setReply(repliedToEvent);
- room->input()->message(replyBody);
- }
+ if (auto room = rooms_->getRoomById(roomid)) {
+ room->setReply(repliedToEvent);
+ room->input()->message(replyBody);
+ }
}
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::msg::CallInvite &callInvite)
{
- if (auto room = rooms_->getRoomById(roomid))
- room->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite);
+ if (auto room = rooms_->getRoomById(roomid))
+ room->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite);
}
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::msg::CallCandidates &callCandidates)
{
- if (auto room = rooms_->getRoomById(roomid))
- room->sendMessageEvent(callCandidates, mtx::events::EventType::CallCandidates);
+ if (auto room = rooms_->getRoomById(roomid))
+ room->sendMessageEvent(callCandidates, mtx::events::EventType::CallCandidates);
}
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::msg::CallAnswer &callAnswer)
{
- if (auto room = rooms_->getRoomById(roomid))
- room->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer);
+ if (auto room = rooms_->getRoomById(roomid))
+ room->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer);
}
void
TimelineViewManager::queueCallMessage(const QString &roomid,
const mtx::events::msg::CallHangUp &callHangUp)
{
- if (auto room = rooms_->getRoomById(roomid))
- room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp);
+ if (auto room = rooms_->getRoomById(roomid))
+ room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp);
}
void
TimelineViewManager::focusMessageInput()
{
- emit focusInput();
+ emit focusInput();
}
QObject *
TimelineViewManager::completerFor(QString completerName, QString roomId)
{
- if (completerName == "user") {
- auto userModel = new UsersModel(roomId.toStdString());
- auto proxy = new CompletionProxyModel(userModel);
- userModel->setParent(proxy);
- return proxy;
- } else if (completerName == "emoji") {
- auto emojiModel = new emoji::EmojiModel();
- auto proxy = new CompletionProxyModel(emojiModel);
- emojiModel->setParent(proxy);
- return proxy;
- } else if (completerName == "allemoji") {
- auto emojiModel = new emoji::EmojiModel();
- auto proxy = new CompletionProxyModel(emojiModel, 1, static_cast<size_t>(-1) / 4);
- emojiModel->setParent(proxy);
- return proxy;
- } else if (completerName == "room") {
- auto roomModel = new RoomsModel(false);
- auto proxy = new CompletionProxyModel(roomModel, 4);
- roomModel->setParent(proxy);
- return proxy;
- } else if (completerName == "roomAliases") {
- auto roomModel = new RoomsModel(true);
- auto proxy = new CompletionProxyModel(roomModel);
- roomModel->setParent(proxy);
- return proxy;
- } else if (completerName == "stickers") {
- auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true);
- auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
- stickerModel->setParent(proxy);
- return proxy;
- }
- return nullptr;
+ if (completerName == "user") {
+ auto userModel = new UsersModel(roomId.toStdString());
+ auto proxy = new CompletionProxyModel(userModel);
+ userModel->setParent(proxy);
+ return proxy;
+ } else if (completerName == "emoji") {
+ auto emojiModel = new emoji::EmojiModel();
+ auto proxy = new CompletionProxyModel(emojiModel);
+ emojiModel->setParent(proxy);
+ return proxy;
+ } else if (completerName == "allemoji") {
+ auto emojiModel = new emoji::EmojiModel();
+ auto proxy = new CompletionProxyModel(emojiModel, 1, static_cast<size_t>(-1) / 4);
+ emojiModel->setParent(proxy);
+ return proxy;
+ } else if (completerName == "room") {
+ auto roomModel = new RoomsModel(false);
+ auto proxy = new CompletionProxyModel(roomModel, 4);
+ roomModel->setParent(proxy);
+ return proxy;
+ } else if (completerName == "roomAliases") {
+ auto roomModel = new RoomsModel(true);
+ auto proxy = new CompletionProxyModel(roomModel);
+ roomModel->setParent(proxy);
+ return proxy;
+ } else if (completerName == "stickers") {
+ auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true);
+ auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
+ stickerModel->setParent(proxy);
+ return proxy;
+ }
+ return nullptr;
}
void
TimelineViewManager::focusTimeline()
{
- getWidget()->setFocus();
+ getWidget()->setFocus();
}
void
TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
QString roomId)
{
- auto room = rooms_->getRoomById(roomId);
- auto content = mtx::accessors::url(*e);
- std::optional<mtx::crypto::EncryptedFile> encryptionInfo = mtx::accessors::file(*e);
-
- if (encryptionInfo) {
- http::client()->download(
- content,
- [this, roomId, e, encryptionInfo](const std::string &res,
- const std::string &content_type,
- const std::string &originalFilename,
- mtx::http::RequestErr err) {
- if (err)
- return;
-
- auto data = mtx::crypto::to_string(
- mtx::crypto::decrypt_file(res, encryptionInfo.value()));
-
- http::client()->upload(
- data,
- content_type,
- originalFilename,
- [this, roomId, e](const mtx::responses::ContentURI &res,
- mtx::http::RequestErr err) mutable {
- if (err) {
- nhlog::net()->warn("failed to upload media: {} {} ({})",
- err->matrix_error.error,
- to_string(err->matrix_error.errcode),
- static_cast<int>(err->status_code));
- return;
- }
-
- std::visit(
- [this, roomId, url = res.content_uri](auto ev) {
- if constexpr (mtx::events::message_content_to_type<
- decltype(ev.content)> ==
- mtx::events::EventType::RoomMessage) {
- if constexpr (messageWithFileAndUrl(ev)) {
- ev.content.relations.relations
- .clear();
- ev.content.file.reset();
- ev.content.url = url;
- }
-
- if (auto room = rooms_->getRoomById(roomId)) {
- removeReplyFallback(ev);
- ev.content.relations.relations
- .clear();
- room->sendMessageEvent(
- ev.content,
- mtx::events::EventType::
- RoomMessage);
- }
- }
- },
- *e);
- });
+ auto room = rooms_->getRoomById(roomId);
+ auto content = mtx::accessors::url(*e);
+ std::optional<mtx::crypto::EncryptedFile> encryptionInfo = mtx::accessors::file(*e);
+
+ if (encryptionInfo) {
+ http::client()->download(
+ content,
+ [this, roomId, e, encryptionInfo](const std::string &res,
+ const std::string &content_type,
+ const std::string &originalFilename,
+ mtx::http::RequestErr err) {
+ if (err)
+ return;
+
+ auto data =
+ mtx::crypto::to_string(mtx::crypto::decrypt_file(res, encryptionInfo.value()));
+
+ http::client()->upload(
+ data,
+ content_type,
+ originalFilename,
+ [this, roomId, e](const mtx::responses::ContentURI &res,
+ mtx::http::RequestErr err) mutable {
+ if (err) {
+ nhlog::net()->warn("failed to upload media: {} {} ({})",
+ err->matrix_error.error,
+ to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ std::visit(
+ [this, roomId, url = res.content_uri](auto ev) {
+ using namespace mtx::events;
+ if constexpr (EventType::RoomMessage ==
+ message_content_to_type<decltype(ev.content)> ||
+ EventType::Sticker ==
+ message_content_to_type<decltype(ev.content)>) {
+ if constexpr (messageWithFileAndUrl(ev)) {
+ ev.content.relations.relations.clear();
+ ev.content.file.reset();
+ ev.content.url = url;
+ }
+
+ if (auto room = rooms_->getRoomById(roomId)) {
+ removeReplyFallback(ev);
+ ev.content.relations.relations.clear();
+ room->sendMessageEvent(ev.content,
+ mtx::events::EventType::RoomMessage);
+ }
+ }
+ },
+ *e);
+ });
- return;
- });
+ return;
+ });
- return;
- }
+ return;
+ }
- std::visit(
- [room](auto e) {
- if constexpr (mtx::events::message_content_to_type<decltype(e.content)> ==
- mtx::events::EventType::RoomMessage) {
- e.content.relations.relations.clear();
- removeReplyFallback(e);
- room->sendMessageEvent(e.content, mtx::events::EventType::RoomMessage);
- }
- },
- *e);
+ std::visit(
+ [room](auto e) {
+ if constexpr (mtx::events::message_content_to_type<decltype(e.content)> ==
+ mtx::events::EventType::RoomMessage) {
+ e.content.relations.relations.clear();
+ removeReplyFallback(e);
+ room->sendMessageEvent(e.content, mtx::events::EventType::RoomMessage);
+ }
+ },
+ *e);
}
//! WORKAROUND(Nico): for https://bugreports.qt.io/browse/QTBUG-93281
void
TimelineViewManager::fixImageRendering(QQuickTextDocument *t, QQuickItem *i)
{
- if (t) {
- QObject::connect(
- t->textDocument(), SIGNAL(imagesLoaded()), i, SLOT(updateWholeDocument()));
- }
+ if (t) {
+ QObject::connect(t->textDocument(), SIGNAL(imagesLoaded()), i, SLOT(updateWholeDocument()));
+ }
}
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 8991de55..f7b01315 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -38,123 +38,121 @@ class ImagePackListModel;
class TimelineViewManager : public QObject
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(
- bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
- Q_PROPERTY(
- bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged)
+ Q_PROPERTY(
+ bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
+ Q_PROPERTY(
+ bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged)
public:
- TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
- QWidget *getWidget() const { return container; }
+ TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
+ QWidget *getWidget() const { return container; }
- void sync(const mtx::responses::Rooms &rooms);
+ void sync(const mtx::responses::Rooms &rooms);
- MxcImageProvider *imageProvider() { return imgProvider; }
- CallManager *callManager() { return callManager_; }
+ MxcImageProvider *imageProvider() { return imgProvider; }
+ CallManager *callManager() { return callManager_; }
- void clearAll() { rooms_->clear(); }
+ void clearAll() { rooms_->clear(); }
- Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
- bool isWindowFocused() const { return isWindowFocused_; }
- Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId);
- Q_INVOKABLE void openImagePackSettings(QString roomid);
- Q_INVOKABLE QColor userColor(QString id, QColor background);
- Q_INVOKABLE QString escapeEmoji(QString str) const;
- Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
+ Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
+ bool isWindowFocused() const { return isWindowFocused_; }
+ Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId);
+ Q_INVOKABLE void openImagePackSettings(QString roomid);
+ Q_INVOKABLE QColor userColor(QString id, QColor background);
+ Q_INVOKABLE QString escapeEmoji(QString str) const;
+ Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
- Q_INVOKABLE QString userPresence(QString id) const;
- Q_INVOKABLE QString userStatus(QString id) const;
+ Q_INVOKABLE QString userPresence(QString id) const;
+ Q_INVOKABLE QString userStatus(QString id) const;
- Q_INVOKABLE void openRoomMembers(TimelineModel *room);
- Q_INVOKABLE void openRoomSettings(QString room_id);
- Q_INVOKABLE void openInviteUsers(QString roomId);
- Q_INVOKABLE void openGlobalUserProfile(QString userId);
+ Q_INVOKABLE void openRoomMembers(TimelineModel *room);
+ Q_INVOKABLE void openRoomSettings(QString room_id);
+ Q_INVOKABLE void openInviteUsers(QString roomId);
+ Q_INVOKABLE void openGlobalUserProfile(QString userId);
- Q_INVOKABLE void focusMessageInput();
- Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const;
- Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
+ Q_INVOKABLE void focusMessageInput();
+ Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const;
+ Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
- Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i);
+ Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i);
- void verifyUser(QString userid);
- void verifyDevice(QString userid, QString deviceid);
+ void verifyUser(QString userid);
+ void verifyDevice(QString userid, QString deviceid);
signals:
- void activeTimelineChanged(TimelineModel *timeline);
- void initialSyncChanged(bool isInitialSync);
- void replyingEventChanged(QString replyingEvent);
- void replyClosed();
- void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
- void inviteUsers(QString roomId, QStringList users);
- void showRoomList();
- void narrowViewChanged();
- void focusChanged();
- void focusInput();
- void openImageOverlayInternalCb(QString eventId, QImage img);
- void openRoomMembersDialog(MemberList *members, TimelineModel *room);
- void openRoomSettingsDialog(RoomSettings *settings);
- void openInviteUsersDialog(InviteesModel *invitees);
- void openProfile(UserProfile *profile);
- void showImagePackSettings(ImagePackListModel *packlist);
+ void activeTimelineChanged(TimelineModel *timeline);
+ void initialSyncChanged(bool isInitialSync);
+ void replyingEventChanged(QString replyingEvent);
+ void replyClosed();
+ void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
+ void inviteUsers(QString roomId, QStringList users);
+ void showRoomList();
+ void narrowViewChanged();
+ void focusChanged();
+ void focusInput();
+ void openImageOverlayInternalCb(QString eventId, QImage img);
+ void openRoomMembersDialog(MemberList *members, TimelineModel *room);
+ void openRoomSettingsDialog(RoomSettings *settings);
+ void openInviteUsersDialog(InviteesModel *invitees);
+ void openProfile(UserProfile *profile);
+ void showImagePackSettings(ImagePackListModel *packlist);
public slots:
- void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
- void receivedSessionKey(const std::string &room_id, const std::string &session_id);
- void initializeRoomlist();
- void chatFocusChanged(bool focused)
- {
- isWindowFocused_ = focused;
- emit focusChanged();
- }
-
- void showEvent(const QString &room_id, const QString &event_id);
- void focusTimeline();
-
- void updateColorPalette();
- void queueReply(const QString &roomid,
- const QString &repliedToEvent,
- const QString &replyBody);
- void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
- void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
- void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
- void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
-
- void setVideoCallItem();
-
- QObject *completerFor(QString completerName, QString roomId = "");
- void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
-
- RoomlistModel *rooms() { return rooms_; }
+ void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
+ void receivedSessionKey(const std::string &room_id, const std::string &session_id);
+ void initializeRoomlist();
+ void chatFocusChanged(bool focused)
+ {
+ isWindowFocused_ = focused;
+ emit focusChanged();
+ }
+
+ void showEvent(const QString &room_id, const QString &event_id);
+ void focusTimeline();
+
+ void updateColorPalette();
+ void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
+
+ void setVideoCallItem();
+
+ QObject *completerFor(QString completerName, QString roomId = "");
+ void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
+
+ RoomlistModel *rooms() { return rooms_; }
private slots:
- void openImageOverlayInternal(QString eventId, QImage img);
+ void openImageOverlayInternal(QString eventId, QImage img);
private:
#ifdef USE_QUICK_VIEW
- QQuickView *view;
+ QQuickView *view;
#else
- QQuickWidget *view;
+ QQuickWidget *view;
#endif
- QWidget *container;
+ QWidget *container;
- MxcImageProvider *imgProvider;
- ColorImageProvider *colorImgProvider;
- BlurhashProvider *blurhashProvider;
- JdenticonProvider *jdenticonProvider;
+ MxcImageProvider *imgProvider;
+ ColorImageProvider *colorImgProvider;
+ BlurhashProvider *blurhashProvider;
+ JdenticonProvider *jdenticonProvider;
- CallManager *callManager_ = nullptr;
+ CallManager *callManager_ = nullptr;
- bool isInitialSync_ = true;
- bool isWindowFocused_ = false;
+ bool isInitialSync_ = true;
+ bool isWindowFocused_ = false;
- RoomlistModel *rooms_ = nullptr;
- CommunitiesModel *communities_ = nullptr;
+ RoomlistModel *rooms_ = nullptr;
+ CommunitiesModel *communities_ = nullptr;
- QHash<QString, QColor> userColors;
+ QHash<QString, QColor> userColors;
- QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList;
+ QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList;
};
Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept)
Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel)
diff --git a/src/ui/Badge.cpp b/src/ui/Badge.cpp
index 66210d06..1b5aba2f 100644
--- a/src/ui/Badge.cpp
+++ b/src/ui/Badge.cpp
@@ -9,214 +9,214 @@
Badge::Badge(QWidget *parent)
: OverlayWidget(parent)
{
- init();
+ init();
}
Badge::Badge(const QIcon &icon, QWidget *parent)
: OverlayWidget(parent)
{
- init();
- setIcon(icon);
+ init();
+ setIcon(icon);
}
Badge::Badge(const QString &text, QWidget *parent)
: OverlayWidget(parent)
{
- init();
- setText(text);
+ init();
+ setText(text);
}
void
Badge::init()
{
- x_ = 0;
- y_ = 0;
- // TODO: Make padding configurable.
- padding_ = 5;
- diameter_ = 24;
+ x_ = 0;
+ y_ = 0;
+ // TODO: Make padding configurable.
+ padding_ = 5;
+ diameter_ = 24;
- setAttribute(Qt::WA_TransparentForMouseEvents);
+ setAttribute(Qt::WA_TransparentForMouseEvents);
- QFont _font(font());
- _font.setPointSizeF(7.5);
- _font.setStyleName("Bold");
+ QFont _font(font());
+ _font.setPointSizeF(7.5);
+ _font.setStyleName("Bold");
- setFont(_font);
- setText("");
+ setFont(_font);
+ setText("");
}
QString
Badge::text() const
{
- return text_;
+ return text_;
}
QIcon
Badge::icon() const
{
- return icon_;
+ return icon_;
}
QSize
Badge::sizeHint() const
{
- const int d = diameter();
- return QSize(d + 4, d + 4);
+ const int d = diameter();
+ return QSize(d + 4, d + 4);
}
qreal
Badge::relativeYPosition() const
{
- return y_;
+ return y_;
}
qreal
Badge::relativeXPosition() const
{
- return x_;
+ return x_;
}
QPointF
Badge::relativePosition() const
{
- return QPointF(x_, y_);
+ return QPointF(x_, y_);
}
QColor
Badge::backgroundColor() const
{
- if (!background_color_.isValid())
- return QColor("black");
+ if (!background_color_.isValid())
+ return QColor("black");
- return background_color_;
+ return background_color_;
}
QColor
Badge::textColor() const
{
- if (!text_color_.isValid())
- return QColor("white");
+ if (!text_color_.isValid())
+ return QColor("white");
- return text_color_;
+ return text_color_;
}
void
Badge::setTextColor(const QColor &color)
{
- text_color_ = color;
+ text_color_ = color;
}
void
Badge::setBackgroundColor(const QColor &color)
{
- background_color_ = color;
+ background_color_ = color;
}
void
Badge::setRelativePosition(const QPointF &pos)
{
- setRelativePosition(pos.x(), pos.y());
+ setRelativePosition(pos.x(), pos.y());
}
void
Badge::setRelativePosition(qreal x, qreal y)
{
- x_ = x;
- y_ = y;
- update();
+ x_ = x;
+ y_ = y;
+ update();
}
void
Badge::setRelativeXPosition(qreal x)
{
- x_ = x;
- update();
+ x_ = x;
+ update();
}
void
Badge::setRelativeYPosition(qreal y)
{
- y_ = y;
- update();
+ y_ = y;
+ update();
}
void
Badge::setIcon(const QIcon &icon)
{
- icon_ = icon;
- update();
+ icon_ = icon;
+ update();
}
void
Badge::setText(const QString &text)
{
- text_ = text;
+ text_ = text;
- if (!icon_.isNull())
- icon_ = QIcon();
+ if (!icon_.isNull())
+ icon_ = QIcon();
- size_ = fontMetrics().size(Qt::TextShowMnemonic, text);
+ size_ = fontMetrics().size(Qt::TextShowMnemonic, text);
- update();
+ update();
}
void
Badge::setDiameter(int diameter)
{
- if (diameter > 0) {
- diameter_ = diameter;
- update();
- }
+ if (diameter > 0) {
+ diameter_ = diameter;
+ update();
+ }
}
void
Badge::paintEvent(QPaintEvent *)
{
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
- painter.translate(x_, y_);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.translate(x_, y_);
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
- painter.setBrush(brush);
- painter.setPen(Qt::NoPen);
+ painter.setBrush(brush);
+ painter.setPen(Qt::NoPen);
- const int d = diameter();
+ const int d = diameter();
- QRectF r(0, 0, d, d);
- r.translate(QPointF((width() - d), (height() - d)) / 2);
+ QRectF r(0, 0, d, d);
+ r.translate(QPointF((width() - d), (height() - d)) / 2);
- if (icon_.isNull()) {
- QPen pen;
- // TODO: Make badge width configurable.
- pen.setWidth(1);
- pen.setColor(textColor());
+ if (icon_.isNull()) {
+ QPen pen;
+ // TODO: Make badge width configurable.
+ pen.setWidth(1);
+ pen.setColor(textColor());
- painter.setPen(pen);
- painter.drawEllipse(r);
+ painter.setPen(pen);
+ painter.drawEllipse(r);
- painter.setPen(textColor());
- painter.setBrush(Qt::NoBrush);
- painter.drawText(r.translated(0, -0.5), Qt::AlignCenter, text_);
- } else {
- painter.drawEllipse(r);
- QRectF q(0, 0, 16, 16);
- q.moveCenter(r.center());
- QPixmap pixmap = icon().pixmap(16, 16);
- QPainter icon(&pixmap);
- icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
- icon.fillRect(pixmap.rect(), textColor());
- painter.drawPixmap(q.toRect(), pixmap);
- }
+ painter.setPen(textColor());
+ painter.setBrush(Qt::NoBrush);
+ painter.drawText(r.translated(0, -0.5), Qt::AlignCenter, text_);
+ } else {
+ painter.drawEllipse(r);
+ QRectF q(0, 0, 16, 16);
+ q.moveCenter(r.center());
+ QPixmap pixmap = icon().pixmap(16, 16);
+ QPainter icon(&pixmap);
+ icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ icon.fillRect(pixmap.rect(), textColor());
+ painter.drawPixmap(q.toRect(), pixmap);
+ }
}
int
Badge::diameter() const
{
- if (icon_.isNull()) {
- return qMax(size_.width(), size_.height()) + padding_;
- }
+ if (icon_.isNull()) {
+ return qMax(size_.width(), size_.height()) + padding_;
+ }
- return diameter_;
+ return diameter_;
}
diff --git a/src/ui/Badge.h b/src/ui/Badge.h
index 98e16873..15b69b58 100644
--- a/src/ui/Badge.h
+++ b/src/ui/Badge.h
@@ -13,54 +13,54 @@
class Badge : public OverlayWidget
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
- Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
- Q_PROPERTY(QPointF relativePosition WRITE setRelativePosition READ relativePosition)
+ Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+ Q_PROPERTY(QPointF relativePosition WRITE setRelativePosition READ relativePosition)
public:
- explicit Badge(QWidget *parent = nullptr);
- explicit Badge(const QIcon &icon, QWidget *parent = nullptr);
- explicit Badge(const QString &text, QWidget *parent = nullptr);
+ explicit Badge(QWidget *parent = nullptr);
+ explicit Badge(const QIcon &icon, QWidget *parent = nullptr);
+ explicit Badge(const QString &text, QWidget *parent = nullptr);
- void setBackgroundColor(const QColor &color);
- void setTextColor(const QColor &color);
- void setIcon(const QIcon &icon);
- void setRelativePosition(const QPointF &pos);
- void setRelativePosition(qreal x, qreal y);
- void setRelativeXPosition(qreal x);
- void setRelativeYPosition(qreal y);
- void setText(const QString &text);
- void setDiameter(int diameter);
+ void setBackgroundColor(const QColor &color);
+ void setTextColor(const QColor &color);
+ void setIcon(const QIcon &icon);
+ void setRelativePosition(const QPointF &pos);
+ void setRelativePosition(qreal x, qreal y);
+ void setRelativeXPosition(qreal x);
+ void setRelativeYPosition(qreal y);
+ void setText(const QString &text);
+ void setDiameter(int diameter);
- QIcon icon() const;
- QString text() const;
- QColor backgroundColor() const;
- QColor textColor() const;
- QPointF relativePosition() const;
- QSize sizeHint() const override;
- qreal relativeXPosition() const;
- qreal relativeYPosition() const;
+ QIcon icon() const;
+ QString text() const;
+ QColor backgroundColor() const;
+ QColor textColor() const;
+ QPointF relativePosition() const;
+ QSize sizeHint() const override;
+ qreal relativeXPosition() const;
+ qreal relativeYPosition() const;
- int diameter() const;
+ int diameter() const;
protected:
- void paintEvent(QPaintEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
private:
- void init();
+ void init();
- QColor background_color_;
- QColor text_color_;
+ QColor background_color_;
+ QColor text_color_;
- QIcon icon_;
- QSize size_;
- QString text_;
+ QIcon icon_;
+ QSize size_;
+ QString text_;
- int padding_;
- int diameter_;
+ int padding_;
+ int diameter_;
- qreal x_;
- qreal y_;
+ qreal x_;
+ qreal y_;
};
diff --git a/src/ui/DropShadow.cpp b/src/ui/DropShadow.cpp
index a413e3f7..31d13b93 100644
--- a/src/ui/DropShadow.cpp
+++ b/src/ui/DropShadow.cpp
@@ -19,94 +19,89 @@ DropShadow::draw(QPainter &painter,
qreal width,
qreal height)
{
- painter.setPen(Qt::NoPen);
+ painter.setPen(Qt::NoPen);
- QLinearGradient gradient;
- gradient.setColorAt(startPosition, start);
- gradient.setColorAt(endPosition0, end);
+ QLinearGradient gradient;
+ gradient.setColorAt(startPosition, start);
+ gradient.setColorAt(endPosition0, end);
- // Right
- QPointF right0(width - margin, height / 2);
- QPointF right1(width, height / 2);
- gradient.setStart(right0);
- gradient.setFinalStop(right1);
- painter.setBrush(QBrush(gradient));
- // Deprecated in 5.13: painter.drawRoundRect(
- // QRectF(QPointF(width - margin * radius, margin), QPointF(width, height -
- // margin)), 0.0, 0.0);
- painter.drawRoundedRect(
- QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)),
- 0.0,
- 0.0);
+ // Right
+ QPointF right0(width - margin, height / 2);
+ QPointF right1(width, height / 2);
+ gradient.setStart(right0);
+ gradient.setFinalStop(right1);
+ painter.setBrush(QBrush(gradient));
+ // Deprecated in 5.13: painter.drawRoundRect(
+ // QRectF(QPointF(width - margin * radius, margin), QPointF(width, height -
+ // margin)), 0.0, 0.0);
+ painter.drawRoundedRect(
+ QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)), 0.0, 0.0);
- // Left
- QPointF left0(margin, height / 2);
- QPointF left1(0, height / 2);
- gradient.setStart(left0);
- gradient.setFinalStop(left1);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(
- QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0);
+ // Left
+ QPointF left0(margin, height / 2);
+ QPointF left1(0, height / 2);
+ gradient.setStart(left0);
+ gradient.setFinalStop(left1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(
+ QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0);
- // Top
- QPointF top0(width / 2, margin);
- QPointF top1(width / 2, 0);
- gradient.setStart(top0);
- gradient.setFinalStop(top1);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(
- QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0);
+ // Top
+ QPointF top0(width / 2, margin);
+ QPointF top1(width / 2, 0);
+ gradient.setStart(top0);
+ gradient.setFinalStop(top1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0);
- // Bottom
- QPointF bottom0(width / 2, height - margin);
- QPointF bottom1(width / 2, height);
- gradient.setStart(bottom0);
- gradient.setFinalStop(bottom1);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(
- QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), 0.0, 0.0);
+ // Bottom
+ QPointF bottom0(width / 2, height - margin);
+ QPointF bottom1(width / 2, height);
+ gradient.setStart(bottom0);
+ gradient.setFinalStop(bottom1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(
+ QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), 0.0, 0.0);
- // BottomRight
- QPointF bottomright0(width - margin, height - margin);
- QPointF bottomright1(width, height);
- gradient.setStart(bottomright0);
- gradient.setFinalStop(bottomright1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(bottomright0, bottomright1), 0.0, 0.0);
+ // BottomRight
+ QPointF bottomright0(width - margin, height - margin);
+ QPointF bottomright1(width, height);
+ gradient.setStart(bottomright0);
+ gradient.setFinalStop(bottomright1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(QRectF(bottomright0, bottomright1), 0.0, 0.0);
- // BottomLeft
- QPointF bottomleft0(margin, height - margin);
- QPointF bottomleft1(0, height);
- gradient.setStart(bottomleft0);
- gradient.setFinalStop(bottomleft1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0);
+ // BottomLeft
+ QPointF bottomleft0(margin, height - margin);
+ QPointF bottomleft1(0, height);
+ gradient.setStart(bottomleft0);
+ gradient.setFinalStop(bottomleft1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0);
- // TopLeft
- QPointF topleft0(margin, margin);
- QPointF topleft1(0, 0);
- gradient.setStart(topleft0);
- gradient.setFinalStop(topleft1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(topleft0, topleft1), 0.0, 0.0);
+ // TopLeft
+ QPointF topleft0(margin, margin);
+ QPointF topleft1(0, 0);
+ gradient.setStart(topleft0);
+ gradient.setFinalStop(topleft1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(QRectF(topleft0, topleft1), 0.0, 0.0);
- // TopRight
- QPointF topright0(width - margin, margin);
- QPointF topright1(width, 0);
- gradient.setStart(topright0);
- gradient.setFinalStop(topright1);
- gradient.setColorAt(endPosition1, end);
- painter.setBrush(QBrush(gradient));
- painter.drawRoundedRect(QRectF(topright0, topright1), 0.0, 0.0);
+ // TopRight
+ QPointF topright0(width - margin, margin);
+ QPointF topright1(width, 0);
+ gradient.setStart(topright0);
+ gradient.setFinalStop(topright1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundedRect(QRectF(topright0, topright1), 0.0, 0.0);
- // Widget
- painter.setBrush(QBrush("#FFFFFF"));
- painter.setRenderHint(QPainter::Antialiasing);
- painter.drawRoundedRect(
- QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)),
- radius,
- radius);
+ // Widget
+ painter.setBrush(QBrush("#FFFFFF"));
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.drawRoundedRect(
+ QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)), radius, radius);
}
diff --git a/src/ui/DropShadow.h b/src/ui/DropShadow.h
index 4ace4731..f089d0b3 100644
--- a/src/ui/DropShadow.h
+++ b/src/ui/DropShadow.h
@@ -11,14 +11,14 @@ class QPainter;
class DropShadow
{
public:
- static void draw(QPainter &painter,
- qint16 margin,
- qreal radius,
- QColor start,
- QColor end,
- qreal startPosition,
- qreal endPosition0,
- qreal endPosition1,
- qreal width,
- qreal height);
+ static void draw(QPainter &painter,
+ qint16 margin,
+ qreal radius,
+ QColor start,
+ QColor end,
+ qreal startPosition,
+ qreal endPosition0,
+ qreal endPosition1,
+ qreal width,
+ qreal height);
};
diff --git a/src/ui/FlatButton.cpp b/src/ui/FlatButton.cpp
index c036401b..4d19c8bb 100644
--- a/src/ui/FlatButton.cpp
+++ b/src/ui/FlatButton.cpp
@@ -30,60 +30,60 @@
static QString
removeKDEAccelerators(QString text)
{
- return text.remove(QChar('&'));
+ return text.remove(QChar('&'));
}
void
FlatButton::init()
{
- ripple_overlay_ = new RippleOverlay(this);
- state_machine_ = new FlatButtonStateMachine(this);
- role_ = ui::Role::Default;
- ripple_style_ = ui::RippleStyle::PositionedRipple;
- icon_placement_ = ui::ButtonIconPlacement::LeftIcon;
- overlay_style_ = ui::OverlayStyle::GrayOverlay;
- bg_mode_ = Qt::TransparentMode;
- fixed_ripple_radius_ = 64;
- corner_radius_ = 3;
- base_opacity_ = 0.13;
- font_size_ = 10; // 10.5;
- use_fixed_ripple_radius_ = false;
-
- setStyle(&ThemeManager::instance());
- setAttribute(Qt::WA_Hover);
- setMouseTracking(true);
- setCursor(QCursor(Qt::PointingHandCursor));
+ ripple_overlay_ = new RippleOverlay(this);
+ state_machine_ = new FlatButtonStateMachine(this);
+ role_ = ui::Role::Default;
+ ripple_style_ = ui::RippleStyle::PositionedRipple;
+ icon_placement_ = ui::ButtonIconPlacement::LeftIcon;
+ overlay_style_ = ui::OverlayStyle::GrayOverlay;
+ bg_mode_ = Qt::TransparentMode;
+ fixed_ripple_radius_ = 64;
+ corner_radius_ = 3;
+ base_opacity_ = 0.13;
+ font_size_ = 10; // 10.5;
+ use_fixed_ripple_radius_ = false;
- QPainterPath path;
- path.addRoundedRect(rect(), corner_radius_, corner_radius_);
+ setStyle(&ThemeManager::instance());
+ setAttribute(Qt::WA_Hover);
+ setMouseTracking(true);
+ setCursor(QCursor(Qt::PointingHandCursor));
+
+ QPainterPath path;
+ path.addRoundedRect(rect(), corner_radius_, corner_radius_);
- ripple_overlay_->setClipPath(path);
- ripple_overlay_->setClipping(true);
+ ripple_overlay_->setClipPath(path);
+ ripple_overlay_->setClipping(true);
- state_machine_->setupProperties();
- state_machine_->startAnimations();
+ state_machine_->setupProperties();
+ state_machine_->startAnimations();
}
FlatButton::FlatButton(QWidget *parent, ui::ButtonPreset preset)
: QPushButton(parent)
{
- init();
- applyPreset(preset);
+ init();
+ applyPreset(preset);
}
FlatButton::FlatButton(const QString &text, QWidget *parent, ui::ButtonPreset preset)
: QPushButton(text, parent)
{
- init();
- applyPreset(preset);
+ init();
+ applyPreset(preset);
}
FlatButton::FlatButton(const QString &text, ui::Role role, QWidget *parent, ui::ButtonPreset preset)
: QPushButton(text, parent)
{
- init();
- applyPreset(preset);
- setRole(role);
+ init();
+ applyPreset(preset);
+ setRole(role);
}
FlatButton::~FlatButton() {}
@@ -91,406 +91,406 @@ FlatButton::~FlatButton() {}
void
FlatButton::applyPreset(ui::ButtonPreset preset)
{
- switch (preset) {
- case ui::ButtonPreset::FlatPreset:
- setOverlayStyle(ui::OverlayStyle::NoOverlay);
- break;
- case ui::ButtonPreset::CheckablePreset:
- setOverlayStyle(ui::OverlayStyle::NoOverlay);
- setCheckable(true);
- break;
- default:
- break;
- }
+ switch (preset) {
+ case ui::ButtonPreset::FlatPreset:
+ setOverlayStyle(ui::OverlayStyle::NoOverlay);
+ break;
+ case ui::ButtonPreset::CheckablePreset:
+ setOverlayStyle(ui::OverlayStyle::NoOverlay);
+ setCheckable(true);
+ break;
+ default:
+ break;
+ }
}
void
FlatButton::setRole(ui::Role role)
{
- role_ = role;
- state_machine_->setupProperties();
+ role_ = role;
+ state_machine_->setupProperties();
}
ui::Role
FlatButton::role() const
{
- return role_;
+ return role_;
}
void
FlatButton::setForegroundColor(const QColor &color)
{
- foreground_color_ = color;
+ foreground_color_ = color;
}
QColor
FlatButton::foregroundColor() const
{
- if (!foreground_color_.isValid()) {
- if (bg_mode_ == Qt::OpaqueMode) {
- return ThemeManager::instance().themeColor("BrightWhite");
- }
-
- switch (role_) {
- case ui::Role::Primary:
- return ThemeManager::instance().themeColor("Blue");
- case ui::Role::Secondary:
- return ThemeManager::instance().themeColor("Gray");
- case ui::Role::Default:
- default:
- return ThemeManager::instance().themeColor("Black");
- }
+ if (!foreground_color_.isValid()) {
+ if (bg_mode_ == Qt::OpaqueMode) {
+ return ThemeManager::instance().themeColor("BrightWhite");
+ }
+
+ switch (role_) {
+ case ui::Role::Primary:
+ return ThemeManager::instance().themeColor("Blue");
+ case ui::Role::Secondary:
+ return ThemeManager::instance().themeColor("Gray");
+ case ui::Role::Default:
+ default:
+ return ThemeManager::instance().themeColor("Black");
}
+ }
- return foreground_color_;
+ return foreground_color_;
}
void
FlatButton::setBackgroundColor(const QColor &color)
{
- background_color_ = color;
+ background_color_ = color;
}
QColor
FlatButton::backgroundColor() const
{
- if (!background_color_.isValid()) {
- switch (role_) {
- case ui::Role::Primary:
- return ThemeManager::instance().themeColor("Blue");
- case ui::Role::Secondary:
- return ThemeManager::instance().themeColor("Gray");
- case ui::Role::Default:
- default:
- return ThemeManager::instance().themeColor("Black");
- }
+ if (!background_color_.isValid()) {
+ switch (role_) {
+ case ui::Role::Primary:
+ return ThemeManager::instance().themeColor("Blue");
+ case ui::Role::Secondary:
+ return ThemeManager::instance().themeColor("Gray");
+ case ui::Role::Default:
+ default:
+ return ThemeManager::instance().themeColor("Black");
}
+ }
- return background_color_;
+ return background_color_;
}
void
FlatButton::setOverlayColor(const QColor &color)
{
- overlay_color_ = color;
- setOverlayStyle(ui::OverlayStyle::TintedOverlay);
+ overlay_color_ = color;
+ setOverlayStyle(ui::OverlayStyle::TintedOverlay);
}
QColor
FlatButton::overlayColor() const
{
- if (!overlay_color_.isValid()) {
- return foregroundColor();
- }
+ if (!overlay_color_.isValid()) {
+ return foregroundColor();
+ }
- return overlay_color_;
+ return overlay_color_;
}
void
FlatButton::setDisabledForegroundColor(const QColor &color)
{
- disabled_color_ = color;
+ disabled_color_ = color;
}
QColor
FlatButton::disabledForegroundColor() const
{
- if (!disabled_color_.isValid()) {
- return ThemeManager::instance().themeColor("FadedWhite");
- }
+ if (!disabled_color_.isValid()) {
+ return ThemeManager::instance().themeColor("FadedWhite");
+ }
- return disabled_color_;
+ return disabled_color_;
}
void
FlatButton::setDisabledBackgroundColor(const QColor &color)
{
- disabled_background_color_ = color;
+ disabled_background_color_ = color;
}
QColor
FlatButton::disabledBackgroundColor() const
{
- if (!disabled_background_color_.isValid()) {
- return ThemeManager::instance().themeColor("FadedWhite");
- }
+ if (!disabled_background_color_.isValid()) {
+ return ThemeManager::instance().themeColor("FadedWhite");
+ }
- return disabled_background_color_;
+ return disabled_background_color_;
}
void
FlatButton::setFontSize(qreal size)
{
- font_size_ = size;
+ font_size_ = size;
- QFont f(font());
- f.setPointSizeF(size);
- setFont(f);
+ QFont f(font());
+ f.setPointSizeF(size);
+ setFont(f);
- update();
+ update();
}
qreal
FlatButton::fontSize() const
{
- return font_size_;
+ return font_size_;
}
void
FlatButton::setOverlayStyle(ui::OverlayStyle style)
{
- overlay_style_ = style;
- update();
+ overlay_style_ = style;
+ update();
}
ui::OverlayStyle
FlatButton::overlayStyle() const
{
- return overlay_style_;
+ return overlay_style_;
}
void
FlatButton::setRippleStyle(ui::RippleStyle style)
{
- ripple_style_ = style;
+ ripple_style_ = style;
}
ui::RippleStyle
FlatButton::rippleStyle() const
{
- return ripple_style_;
+ return ripple_style_;
}
void
FlatButton::setIconPlacement(ui::ButtonIconPlacement placement)
{
- icon_placement_ = placement;
- update();
+ icon_placement_ = placement;
+ update();
}
ui::ButtonIconPlacement
FlatButton::iconPlacement() const
{
- return icon_placement_;
+ return icon_placement_;
}
void
FlatButton::setCornerRadius(qreal radius)
{
- corner_radius_ = radius;
- updateClipPath();
- update();
+ corner_radius_ = radius;
+ updateClipPath();
+ update();
}
qreal
FlatButton::cornerRadius() const
{
- return corner_radius_;
+ return corner_radius_;
}
void
FlatButton::setBackgroundMode(Qt::BGMode mode)
{
- bg_mode_ = mode;
- state_machine_->setupProperties();
+ bg_mode_ = mode;
+ state_machine_->setupProperties();
}
Qt::BGMode
FlatButton::backgroundMode() const
{
- return bg_mode_;
+ return bg_mode_;
}
void
FlatButton::setBaseOpacity(qreal opacity)
{
- base_opacity_ = opacity;
- state_machine_->setupProperties();
+ base_opacity_ = opacity;
+ state_machine_->setupProperties();
}
qreal
FlatButton::baseOpacity() const
{
- return base_opacity_;
+ return base_opacity_;
}
void
FlatButton::setCheckable(bool value)
{
- state_machine_->updateCheckedStatus();
- state_machine_->setCheckedOverlayProgress(0);
+ state_machine_->updateCheckedStatus();
+ state_machine_->setCheckedOverlayProgress(0);
- QPushButton::setCheckable(value);
+ QPushButton::setCheckable(value);
}
void
FlatButton::setHasFixedRippleRadius(bool value)
{
- use_fixed_ripple_radius_ = value;
+ use_fixed_ripple_radius_ = value;
}
bool
FlatButton::hasFixedRippleRadius() const
{
- return use_fixed_ripple_radius_;
+ return use_fixed_ripple_radius_;
}
void
FlatButton::setFixedRippleRadius(qreal radius)
{
- fixed_ripple_radius_ = radius;
- setHasFixedRippleRadius(true);
+ fixed_ripple_radius_ = radius;
+ setHasFixedRippleRadius(true);
}
QSize
FlatButton::sizeHint() const
{
- ensurePolished();
+ ensurePolished();
- QSize label(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text())));
+ QSize label(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text())));
- int w = 20 + label.width();
- int h = label.height();
+ int w = 20 + label.width();
+ int h = label.height();
- if (!icon().isNull()) {
- w += iconSize().width() + FlatButton::IconPadding;
- h = qMax(h, iconSize().height());
- }
+ if (!icon().isNull()) {
+ w += iconSize().width() + FlatButton::IconPadding;
+ h = qMax(h, iconSize().height());
+ }
- return QSize(w, 20 + h);
+ return QSize(w, 20 + h);
}
void
FlatButton::checkStateSet()
{
- state_machine_->updateCheckedStatus();
- QPushButton::checkStateSet();
+ state_machine_->updateCheckedStatus();
+ QPushButton::checkStateSet();
}
void
FlatButton::mousePressEvent(QMouseEvent *event)
{
- if (ui::RippleStyle::NoRipple != ripple_style_) {
- QPoint pos;
- qreal radiusEndValue;
+ if (ui::RippleStyle::NoRipple != ripple_style_) {
+ QPoint pos;
+ qreal radiusEndValue;
- if (ui::RippleStyle::CenteredRipple == ripple_style_) {
- pos = rect().center();
- } else {
- pos = event->pos();
- }
+ if (ui::RippleStyle::CenteredRipple == ripple_style_) {
+ pos = rect().center();
+ } else {
+ pos = event->pos();
+ }
- if (use_fixed_ripple_radius_) {
- radiusEndValue = fixed_ripple_radius_;
- } else {
- radiusEndValue = static_cast<qreal>(width()) / 2;
- }
+ if (use_fixed_ripple_radius_) {
+ radiusEndValue = fixed_ripple_radius_;
+ } else {
+ radiusEndValue = static_cast<qreal>(width()) / 2;
+ }
- Ripple *ripple = new Ripple(pos);
+ Ripple *ripple = new Ripple(pos);
- ripple->setRadiusEndValue(radiusEndValue);
- ripple->setOpacityStartValue(0.35);
- ripple->setColor(foregroundColor());
- ripple->radiusAnimation()->setDuration(250);
- ripple->opacityAnimation()->setDuration(250);
+ ripple->setRadiusEndValue(radiusEndValue);
+ ripple->setOpacityStartValue(0.35);
+ ripple->setColor(foregroundColor());
+ ripple->radiusAnimation()->setDuration(250);
+ ripple->opacityAnimation()->setDuration(250);
- ripple_overlay_->addRipple(ripple);
- }
+ ripple_overlay_->addRipple(ripple);
+ }
- QPushButton::mousePressEvent(event);
+ QPushButton::mousePressEvent(event);
}
void
FlatButton::mouseReleaseEvent(QMouseEvent *event)
{
- QPushButton::mouseReleaseEvent(event);
- state_machine_->updateCheckedStatus();
+ QPushButton::mouseReleaseEvent(event);
+ state_machine_->updateCheckedStatus();
}
void
FlatButton::resizeEvent(QResizeEvent *event)
{
- QPushButton::resizeEvent(event);
- updateClipPath();
+ QPushButton::resizeEvent(event);
+ updateClipPath();
}
void
FlatButton::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event)
+ Q_UNUSED(event)
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
- const qreal cr = corner_radius_;
+ const qreal cr = corner_radius_;
- if (cr > 0) {
- QPainterPath path;
- path.addRoundedRect(rect(), cr, cr);
+ if (cr > 0) {
+ QPainterPath path;
+ path.addRoundedRect(rect(), cr, cr);
- painter.setClipPath(path);
- painter.setClipping(true);
- }
+ painter.setClipPath(path);
+ painter.setClipping(true);
+ }
- paintBackground(&painter);
+ paintBackground(&painter);
- painter.setOpacity(1);
- painter.setClipping(false);
+ painter.setOpacity(1);
+ painter.setClipping(false);
- paintForeground(&painter);
+ paintForeground(&painter);
}
void
FlatButton::paintBackground(QPainter *painter)
{
- const qreal overlayOpacity = state_machine_->overlayOpacity();
- const qreal checkedProgress = state_machine_->checkedOverlayProgress();
+ const qreal overlayOpacity = state_machine_->overlayOpacity();
+ const qreal checkedProgress = state_machine_->checkedOverlayProgress();
- if (Qt::OpaqueMode == bg_mode_) {
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
-
- if (isEnabled()) {
- brush.setColor(backgroundColor());
- } else {
- brush.setColor(disabledBackgroundColor());
- }
+ if (Qt::OpaqueMode == bg_mode_) {
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
- painter->setOpacity(1);
- painter->setBrush(brush);
- painter->setPen(Qt::NoPen);
- painter->drawRect(rect());
+ if (isEnabled()) {
+ brush.setColor(backgroundColor());
+ } else {
+ brush.setColor(disabledBackgroundColor());
}
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
+ painter->setOpacity(1);
+ painter->setBrush(brush);
painter->setPen(Qt::NoPen);
+ painter->drawRect(rect());
+ }
- if (!isEnabled()) {
- return;
- }
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
+ painter->setPen(Qt::NoPen);
- if ((ui::OverlayStyle::NoOverlay != overlay_style_) && (overlayOpacity > 0)) {
- if (ui::OverlayStyle::TintedOverlay == overlay_style_) {
- brush.setColor(overlayColor());
- } else {
- brush.setColor(Qt::gray);
- }
+ if (!isEnabled()) {
+ return;
+ }
- painter->setOpacity(overlayOpacity);
- painter->setBrush(brush);
- painter->drawRect(rect());
+ if ((ui::OverlayStyle::NoOverlay != overlay_style_) && (overlayOpacity > 0)) {
+ if (ui::OverlayStyle::TintedOverlay == overlay_style_) {
+ brush.setColor(overlayColor());
+ } else {
+ brush.setColor(Qt::gray);
}
- if (isCheckable() && checkedProgress > 0) {
- const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7;
- brush.setColor(foregroundColor());
- painter->setOpacity(q * checkedProgress);
- painter->setBrush(brush);
- QRect r(rect());
- r.setHeight(static_cast<qreal>(r.height()) * checkedProgress);
- painter->drawRect(r);
- }
+ painter->setOpacity(overlayOpacity);
+ painter->setBrush(brush);
+ painter->drawRect(rect());
+ }
+
+ if (isCheckable() && checkedProgress > 0) {
+ const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7;
+ brush.setColor(foregroundColor());
+ painter->setOpacity(q * checkedProgress);
+ painter->setBrush(brush);
+ QRect r(rect());
+ r.setHeight(static_cast<qreal>(r.height()) * checkedProgress);
+ painter->drawRect(r);
+ }
}
#define COLOR_INTERPOLATE(CH) (1 - progress) * source.CH() + progress *dest.CH()
@@ -498,64 +498,63 @@ FlatButton::paintBackground(QPainter *painter)
void
FlatButton::paintForeground(QPainter *painter)
{
- if (isEnabled()) {
- painter->setPen(foregroundColor());
- const qreal progress = state_machine_->checkedOverlayProgress();
-
- if (isCheckable() && progress > 0) {
- QColor source = foregroundColor();
- QColor dest =
- Qt::TransparentMode == bg_mode_ ? Qt::white : backgroundColor();
- if (qFuzzyCompare(1, progress)) {
- painter->setPen(dest);
- } else {
- painter->setPen(QColor(COLOR_INTERPOLATE(red),
- COLOR_INTERPOLATE(green),
- COLOR_INTERPOLATE(blue),
- COLOR_INTERPOLATE(alpha)));
- }
- }
- } else {
- painter->setPen(disabledForegroundColor());
+ if (isEnabled()) {
+ painter->setPen(foregroundColor());
+ const qreal progress = state_machine_->checkedOverlayProgress();
+
+ if (isCheckable() && progress > 0) {
+ QColor source = foregroundColor();
+ QColor dest = Qt::TransparentMode == bg_mode_ ? Qt::white : backgroundColor();
+ if (qFuzzyCompare(1, progress)) {
+ painter->setPen(dest);
+ } else {
+ painter->setPen(QColor(COLOR_INTERPOLATE(red),
+ COLOR_INTERPOLATE(green),
+ COLOR_INTERPOLATE(blue),
+ COLOR_INTERPOLATE(alpha)));
+ }
}
+ } else {
+ painter->setPen(disabledForegroundColor());
+ }
- if (icon().isNull()) {
- painter->drawText(rect(), Qt::AlignCenter, removeKDEAccelerators(text()));
- return;
- }
+ if (icon().isNull()) {
+ painter->drawText(rect(), Qt::AlignCenter, removeKDEAccelerators(text()));
+ return;
+ }
- QSize textSize(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text())));
- QSize base(size() - textSize);
+ QSize textSize(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text())));
+ QSize base(size() - textSize);
- const int iw = iconSize().width() + IconPadding;
- QPoint pos((base.width() - iw) / 2, 0);
+ const int iw = iconSize().width() + IconPadding;
+ QPoint pos((base.width() - iw) / 2, 0);
- QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize);
- QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize());
+ QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize);
+ QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize());
- /* if (ui::LeftIcon == icon_placement_) { */
- /* textGeometry.translate(iw, 0); */
- /* } else { */
- /* iconGeometry.translate(textSize.width() + IconPadding, 0); */
- /* } */
+ /* if (ui::LeftIcon == icon_placement_) { */
+ /* textGeometry.translate(iw, 0); */
+ /* } else { */
+ /* iconGeometry.translate(textSize.width() + IconPadding, 0); */
+ /* } */
- painter->drawText(textGeometry, Qt::AlignCenter, removeKDEAccelerators(text()));
+ painter->drawText(textGeometry, Qt::AlignCenter, removeKDEAccelerators(text()));
- QPixmap pixmap = icon().pixmap(iconSize());
- QPainter icon(&pixmap);
- icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
- icon.fillRect(pixmap.rect(), painter->pen().color());
- painter->drawPixmap(iconGeometry, pixmap);
+ QPixmap pixmap = icon().pixmap(iconSize());
+ QPainter icon(&pixmap);
+ icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ icon.fillRect(pixmap.rect(), painter->pen().color());
+ painter->drawPixmap(iconGeometry, pixmap);
}
void
FlatButton::updateClipPath()
{
- const qreal radius = corner_radius_;
+ const qreal radius = corner_radius_;
- QPainterPath path;
- path.addRoundedRect(rect(), radius, radius);
- ripple_overlay_->setClipPath(path);
+ QPainterPath path;
+ path.addRoundedRect(rect(), radius, radius);
+ ripple_overlay_->setClipPath(path);
}
FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent)
@@ -575,45 +574,45 @@ FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent)
, checked_overlay_progress_(parent->isChecked() ? 1 : 0)
, was_checked_(false)
{
- Q_ASSERT(parent);
+ Q_ASSERT(parent);
- parent->installEventFilter(this);
+ parent->installEventFilter(this);
- config_state_->setInitialState(neutral_state_);
- addState(top_level_state_);
- setInitialState(top_level_state_);
+ config_state_->setInitialState(neutral_state_);
+ addState(top_level_state_);
+ setInitialState(top_level_state_);
- checkable_state_->setInitialState(parent->isChecked() ? checked_state_ : unchecked_state_);
- QSignalTransition *transition;
- QPropertyAnimation *animation;
+ checkable_state_->setInitialState(parent->isChecked() ? checked_state_ : unchecked_state_);
+ QSignalTransition *transition;
+ QPropertyAnimation *animation;
- transition = new QSignalTransition(this, SIGNAL(buttonChecked()));
- transition->setTargetState(checked_state_);
- unchecked_state_->addTransition(transition);
+ transition = new QSignalTransition(this, SIGNAL(buttonChecked()));
+ transition->setTargetState(checked_state_);
+ unchecked_state_->addTransition(transition);
- animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
- animation->setDuration(200);
- transition->addAnimation(animation);
+ animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
+ animation->setDuration(200);
+ transition->addAnimation(animation);
- transition = new QSignalTransition(this, SIGNAL(buttonUnchecked()));
- transition->setTargetState(unchecked_state_);
- checked_state_->addTransition(transition);
+ transition = new QSignalTransition(this, SIGNAL(buttonUnchecked()));
+ transition->setTargetState(unchecked_state_);
+ checked_state_->addTransition(transition);
- animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
- animation->setDuration(200);
- transition->addAnimation(animation);
+ animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
+ animation->setDuration(200);
+ transition->addAnimation(animation);
- addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_);
- addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_);
- addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_);
- addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_);
- addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_);
- addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_);
- addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_);
- addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_);
- addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_);
- addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_);
- addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_);
+ addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_);
+ addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_);
+ addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_);
+ addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_);
+ addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_);
+ addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_);
+ addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_);
+ addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_);
+ addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_);
+ addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_);
+ addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_);
}
FlatButtonStateMachine::~FlatButtonStateMachine() {}
@@ -621,73 +620,73 @@ FlatButtonStateMachine::~FlatButtonStateMachine() {}
void
FlatButtonStateMachine::setOverlayOpacity(qreal opacity)
{
- overlay_opacity_ = opacity;
- button_->update();
+ overlay_opacity_ = opacity;
+ button_->update();
}
void
FlatButtonStateMachine::setCheckedOverlayProgress(qreal opacity)
{
- checked_overlay_progress_ = opacity;
- button_->update();
+ checked_overlay_progress_ = opacity;
+ button_->update();
}
void
FlatButtonStateMachine::startAnimations()
{
- start();
+ start();
}
void
FlatButtonStateMachine::setupProperties()
{
- QColor overlayColor;
+ QColor overlayColor;
- if (Qt::TransparentMode == button_->backgroundMode()) {
- overlayColor = button_->backgroundColor();
- } else {
- overlayColor = button_->foregroundColor();
- }
+ if (Qt::TransparentMode == button_->backgroundMode()) {
+ overlayColor = button_->backgroundColor();
+ } else {
+ overlayColor = button_->foregroundColor();
+ }
- const qreal baseOpacity = button_->baseOpacity();
+ const qreal baseOpacity = button_->baseOpacity();
- neutral_state_->assignProperty(this, "overlayOpacity", 0);
- neutral_focused_state_->assignProperty(this, "overlayOpacity", 0);
- hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity);
- hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity);
- pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity);
- checked_state_->assignProperty(this, "checkedOverlayProgress", 1);
- unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0);
+ neutral_state_->assignProperty(this, "overlayOpacity", 0);
+ neutral_focused_state_->assignProperty(this, "overlayOpacity", 0);
+ hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity);
+ hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity);
+ pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity);
+ checked_state_->assignProperty(this, "checkedOverlayProgress", 1);
+ unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0);
- button_->update();
+ button_->update();
}
void
FlatButtonStateMachine::updateCheckedStatus()
{
- const bool checked = button_->isChecked();
- if (was_checked_ != checked) {
- was_checked_ = checked;
- if (checked) {
- emit buttonChecked();
- } else {
- emit buttonUnchecked();
- }
+ const bool checked = button_->isChecked();
+ if (was_checked_ != checked) {
+ was_checked_ = checked;
+ if (checked) {
+ emit buttonChecked();
+ } else {
+ emit buttonUnchecked();
}
+ }
}
bool
FlatButtonStateMachine::eventFilter(QObject *watched, QEvent *event)
{
- if (QEvent::FocusIn == event->type()) {
- QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
- if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) {
- emit buttonPressed();
- return true;
- }
+ if (QEvent::FocusIn == event->type()) {
+ QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
+ if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) {
+ emit buttonPressed();
+ return true;
}
+ }
- return QStateMachine::eventFilter(watched, event);
+ return QStateMachine::eventFilter(watched, event);
}
void
@@ -696,7 +695,7 @@ FlatButtonStateMachine::addTransition(QObject *object,
QState *fromState,
QState *toState)
{
- addTransition(new QSignalTransition(object, signal), fromState, toState);
+ addTransition(new QSignalTransition(object, signal), fromState, toState);
}
void
@@ -705,7 +704,7 @@ FlatButtonStateMachine::addTransition(QObject *object,
QState *fromState,
QState *toState)
{
- addTransition(new QEventTransition(object, eventType), fromState, toState);
+ addTransition(new QEventTransition(object, eventType), fromState, toState);
}
void
@@ -713,13 +712,13 @@ FlatButtonStateMachine::addTransition(QAbstractTransition *transition,
QState *fromState,
QState *toState)
{
- transition->setTargetState(toState);
+ transition->setTargetState(toState);
- QPropertyAnimation *animation;
+ QPropertyAnimation *animation;
- animation = new QPropertyAnimation(this, "overlayOpacity", this);
- animation->setDuration(150);
- transition->addAnimation(animation);
+ animation = new QPropertyAnimation(this, "overlayOpacity", this);
+ animation->setDuration(150);
+ transition->addAnimation(animation);
- fromState->addTransition(transition);
+ fromState->addTransition(transition);
}
diff --git a/src/ui/FlatButton.h b/src/ui/FlatButton.h
index c79945b7..b39c94ac 100644
--- a/src/ui/FlatButton.h
+++ b/src/ui/FlatButton.h
@@ -14,174 +14,171 @@ class FlatButton;
class FlatButtonStateMachine : public QStateMachine
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity)
- Q_PROPERTY(
- qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ checkedOverlayProgress)
+ Q_PROPERTY(qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity)
+ Q_PROPERTY(
+ qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ checkedOverlayProgress)
public:
- explicit FlatButtonStateMachine(FlatButton *parent);
- ~FlatButtonStateMachine() override;
+ explicit FlatButtonStateMachine(FlatButton *parent);
+ ~FlatButtonStateMachine() override;
- void setOverlayOpacity(qreal opacity);
- void setCheckedOverlayProgress(qreal opacity);
+ void setOverlayOpacity(qreal opacity);
+ void setCheckedOverlayProgress(qreal opacity);
- inline qreal overlayOpacity() const;
- inline qreal checkedOverlayProgress() const;
+ inline qreal overlayOpacity() const;
+ inline qreal checkedOverlayProgress() const;
- void startAnimations();
- void setupProperties();
- void updateCheckedStatus();
+ void startAnimations();
+ void setupProperties();
+ void updateCheckedStatus();
signals:
- void buttonPressed();
- void buttonChecked();
- void buttonUnchecked();
+ void buttonPressed();
+ void buttonChecked();
+ void buttonUnchecked();
protected:
- bool eventFilter(QObject *watched, QEvent *event) override;
+ bool eventFilter(QObject *watched, QEvent *event) override;
private:
- void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState);
- void addTransition(QObject *object,
- QEvent::Type eventType,
- QState *fromState,
- QState *toState);
- void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState);
-
- FlatButton *const button_;
-
- QState *const top_level_state_;
- QState *const config_state_;
- QState *const checkable_state_;
- QState *const checked_state_;
- QState *const unchecked_state_;
- QState *const neutral_state_;
- QState *const neutral_focused_state_;
- QState *const hovered_state_;
- QState *const hovered_focused_state_;
- QState *const pressed_state_;
-
- qreal overlay_opacity_;
- qreal checked_overlay_progress_;
-
- bool was_checked_;
+ void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState);
+ void addTransition(QObject *object, QEvent::Type eventType, QState *fromState, QState *toState);
+ void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState);
+
+ FlatButton *const button_;
+
+ QState *const top_level_state_;
+ QState *const config_state_;
+ QState *const checkable_state_;
+ QState *const checked_state_;
+ QState *const unchecked_state_;
+ QState *const neutral_state_;
+ QState *const neutral_focused_state_;
+ QState *const hovered_state_;
+ QState *const hovered_focused_state_;
+ QState *const pressed_state_;
+
+ qreal overlay_opacity_;
+ qreal checked_overlay_progress_;
+
+ bool was_checked_;
};
inline qreal
FlatButtonStateMachine::overlayOpacity() const
{
- return overlay_opacity_;
+ return overlay_opacity_;
}
inline qreal
FlatButtonStateMachine::checkedOverlayProgress() const
{
- return checked_overlay_progress_;
+ return checked_overlay_progress_;
}
class FlatButton : public QPushButton
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor)
- Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
- Q_PROPERTY(QColor overlayColor WRITE setOverlayColor READ overlayColor)
- Q_PROPERTY(QColor disabledForegroundColor WRITE setDisabledForegroundColor READ
- disabledForegroundColor)
- Q_PROPERTY(QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ
- disabledBackgroundColor)
- Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize)
+ Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+ Q_PROPERTY(QColor overlayColor WRITE setOverlayColor READ overlayColor)
+ Q_PROPERTY(
+ QColor disabledForegroundColor WRITE setDisabledForegroundColor READ disabledForegroundColor)
+ Q_PROPERTY(
+ QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ disabledBackgroundColor)
+ Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize)
public:
- explicit FlatButton(QWidget *parent = nullptr,
- ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
- explicit FlatButton(const QString &text,
- QWidget *parent = nullptr,
- ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
- FlatButton(const QString &text,
- ui::Role role,
- QWidget *parent = nullptr,
- ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
- ~FlatButton() override;
-
- void applyPreset(ui::ButtonPreset preset);
-
- void setBackgroundColor(const QColor &color);
- void setBackgroundMode(Qt::BGMode mode);
- void setBaseOpacity(qreal opacity);
- void setCheckable(bool value);
- void setCornerRadius(qreal radius);
- void setDisabledBackgroundColor(const QColor &color);
- void setDisabledForegroundColor(const QColor &color);
- void setFixedRippleRadius(qreal radius);
- void setFontSize(qreal size);
- void setForegroundColor(const QColor &color);
- void setHasFixedRippleRadius(bool value);
- void setIconPlacement(ui::ButtonIconPlacement placement);
- void setOverlayColor(const QColor &color);
- void setOverlayStyle(ui::OverlayStyle style);
- void setRippleStyle(ui::RippleStyle style);
- void setRole(ui::Role role);
-
- QColor foregroundColor() const;
- QColor backgroundColor() const;
- QColor overlayColor() const;
- QColor disabledForegroundColor() const;
- QColor disabledBackgroundColor() const;
-
- qreal fontSize() const;
- qreal cornerRadius() const;
- qreal baseOpacity() const;
-
- bool hasFixedRippleRadius() const;
-
- ui::Role role() const;
- ui::OverlayStyle overlayStyle() const;
- ui::RippleStyle rippleStyle() const;
- ui::ButtonIconPlacement iconPlacement() const;
-
- Qt::BGMode backgroundMode() const;
-
- QSize sizeHint() const override;
+ explicit FlatButton(QWidget *parent = nullptr,
+ ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
+ explicit FlatButton(const QString &text,
+ QWidget *parent = nullptr,
+ ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
+ FlatButton(const QString &text,
+ ui::Role role,
+ QWidget *parent = nullptr,
+ ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
+ ~FlatButton() override;
+
+ void applyPreset(ui::ButtonPreset preset);
+
+ void setBackgroundColor(const QColor &color);
+ void setBackgroundMode(Qt::BGMode mode);
+ void setBaseOpacity(qreal opacity);
+ void setCheckable(bool value);
+ void setCornerRadius(qreal radius);
+ void setDisabledBackgroundColor(const QColor &color);
+ void setDisabledForegroundColor(const QColor &color);
+ void setFixedRippleRadius(qreal radius);
+ void setFontSize(qreal size);
+ void setForegroundColor(const QColor &color);
+ void setHasFixedRippleRadius(bool value);
+ void setIconPlacement(ui::ButtonIconPlacement placement);
+ void setOverlayColor(const QColor &color);
+ void setOverlayStyle(ui::OverlayStyle style);
+ void setRippleStyle(ui::RippleStyle style);
+ void setRole(ui::Role role);
+
+ QColor foregroundColor() const;
+ QColor backgroundColor() const;
+ QColor overlayColor() const;
+ QColor disabledForegroundColor() const;
+ QColor disabledBackgroundColor() const;
+
+ qreal fontSize() const;
+ qreal cornerRadius() const;
+ qreal baseOpacity() const;
+
+ bool hasFixedRippleRadius() const;
+
+ ui::Role role() const;
+ ui::OverlayStyle overlayStyle() const;
+ ui::RippleStyle rippleStyle() const;
+ ui::ButtonIconPlacement iconPlacement() const;
+
+ Qt::BGMode backgroundMode() const;
+
+ QSize sizeHint() const override;
protected:
- int IconPadding = 0;
+ int IconPadding = 0;
- void checkStateSet() override;
- void mousePressEvent(QMouseEvent *event) override;
- void mouseReleaseEvent(QMouseEvent *event) override;
- void resizeEvent(QResizeEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
+ void checkStateSet() override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
- virtual void paintBackground(QPainter *painter);
- virtual void paintForeground(QPainter *painter);
- virtual void updateClipPath();
+ virtual void paintBackground(QPainter *painter);
+ virtual void paintForeground(QPainter *painter);
+ virtual void updateClipPath();
- void init();
+ void init();
private:
- RippleOverlay *ripple_overlay_;
- FlatButtonStateMachine *state_machine_;
+ RippleOverlay *ripple_overlay_;
+ FlatButtonStateMachine *state_machine_;
- ui::Role role_;
- ui::RippleStyle ripple_style_;
- ui::ButtonIconPlacement icon_placement_;
- ui::OverlayStyle overlay_style_;
+ ui::Role role_;
+ ui::RippleStyle ripple_style_;
+ ui::ButtonIconPlacement icon_placement_;
+ ui::OverlayStyle overlay_style_;
- Qt::BGMode bg_mode_;
+ Qt::BGMode bg_mode_;
- QColor background_color_;
- QColor foreground_color_;
- QColor overlay_color_;
- QColor disabled_color_;
- QColor disabled_background_color_;
+ QColor background_color_;
+ QColor foreground_color_;
+ QColor overlay_color_;
+ QColor disabled_color_;
+ QColor disabled_background_color_;
- qreal fixed_ripple_radius_;
- qreal corner_radius_;
- qreal base_opacity_;
- qreal font_size_;
+ qreal fixed_ripple_radius_;
+ qreal corner_radius_;
+ qreal base_opacity_;
+ qreal font_size_;
- bool use_fixed_ripple_radius_;
+ bool use_fixed_ripple_radius_;
};
diff --git a/src/ui/FloatingButton.cpp b/src/ui/FloatingButton.cpp
index 95b6ae1d..3f88e313 100644
--- a/src/ui/FloatingButton.cpp
+++ b/src/ui/FloatingButton.cpp
@@ -10,91 +10,91 @@
FloatingButton::FloatingButton(const QIcon &icon, QWidget *parent)
: RaisedButton(parent)
{
- setFixedSize(DIAMETER, DIAMETER);
- setGeometry(buttonGeometry());
+ setFixedSize(DIAMETER, DIAMETER);
+ setGeometry(buttonGeometry());
- if (parentWidget())
- parentWidget()->installEventFilter(this);
+ if (parentWidget())
+ parentWidget()->installEventFilter(this);
- setFixedRippleRadius(50);
- setIcon(icon);
- raise();
+ setFixedRippleRadius(50);
+ setIcon(icon);
+ raise();
}
QRect
FloatingButton::buttonGeometry() const
{
- QWidget *parent = parentWidget();
+ QWidget *parent = parentWidget();
- if (!parent)
- return QRect();
+ if (!parent)
+ return QRect();
- return QRect(parent->width() - (OFFSET_X + DIAMETER),
- parent->height() - (OFFSET_Y + DIAMETER),
- DIAMETER,
- DIAMETER);
+ return QRect(parent->width() - (OFFSET_X + DIAMETER),
+ parent->height() - (OFFSET_Y + DIAMETER),
+ DIAMETER,
+ DIAMETER);
}
bool
FloatingButton::event(QEvent *event)
{
- if (!parent())
- return RaisedButton::event(event);
-
- switch (event->type()) {
- case QEvent::ParentChange: {
- parent()->installEventFilter(this);
- setGeometry(buttonGeometry());
- break;
- }
- case QEvent::ParentAboutToChange: {
- parent()->installEventFilter(this);
- break;
- }
- default:
- break;
- }
-
+ if (!parent())
return RaisedButton::event(event);
+
+ switch (event->type()) {
+ case QEvent::ParentChange: {
+ parent()->installEventFilter(this);
+ setGeometry(buttonGeometry());
+ break;
+ }
+ case QEvent::ParentAboutToChange: {
+ parent()->installEventFilter(this);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return RaisedButton::event(event);
}
bool
FloatingButton::eventFilter(QObject *obj, QEvent *event)
{
- const QEvent::Type type = event->type();
+ const QEvent::Type type = event->type();
- if (QEvent::Move == type || QEvent::Resize == type)
- setGeometry(buttonGeometry());
+ if (QEvent::Move == type || QEvent::Resize == type)
+ setGeometry(buttonGeometry());
- return RaisedButton::eventFilter(obj, event);
+ return RaisedButton::eventFilter(obj, event);
}
void
FloatingButton::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
- QRect square = QRect(0, 0, DIAMETER, DIAMETER);
- square.moveCenter(rect().center());
+ QRect square = QRect(0, 0, DIAMETER, DIAMETER);
+ square.moveCenter(rect().center());
- QPainter p(this);
- p.setRenderHints(QPainter::Antialiasing);
+ QPainter p(this);
+ p.setRenderHints(QPainter::Antialiasing);
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
- brush.setColor(backgroundColor());
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
+ brush.setColor(backgroundColor());
- p.setBrush(brush);
- p.setPen(Qt::NoPen);
- p.drawEllipse(square);
+ p.setBrush(brush);
+ p.setPen(Qt::NoPen);
+ p.drawEllipse(square);
- QRect iconGeometry(0, 0, ICON_SIZE, ICON_SIZE);
- iconGeometry.moveCenter(square.center());
+ QRect iconGeometry(0, 0, ICON_SIZE, ICON_SIZE);
+ iconGeometry.moveCenter(square.center());
- QPixmap pixmap = icon().pixmap(QSize(ICON_SIZE, ICON_SIZE));
- QPainter icon(&pixmap);
- icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
- icon.fillRect(pixmap.rect(), foregroundColor());
+ QPixmap pixmap = icon().pixmap(QSize(ICON_SIZE, ICON_SIZE));
+ QPainter icon(&pixmap);
+ icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ icon.fillRect(pixmap.rect(), foregroundColor());
- p.drawPixmap(iconGeometry, pixmap);
+ p.drawPixmap(iconGeometry, pixmap);
}
diff --git a/src/ui/FloatingButton.h b/src/ui/FloatingButton.h
index b59b3854..df14dd2c 100644
--- a/src/ui/FloatingButton.h
+++ b/src/ui/FloatingButton.h
@@ -14,17 +14,17 @@ constexpr int OFFSET_Y = 20;
class FloatingButton : public RaisedButton
{
- Q_OBJECT
+ Q_OBJECT
public:
- FloatingButton(const QIcon &icon, QWidget *parent = nullptr);
+ FloatingButton(const QIcon &icon, QWidget *parent = nullptr);
- QSize sizeHint() const override { return QSize(DIAMETER, DIAMETER); };
- QRect buttonGeometry() const;
+ QSize sizeHint() const override { return QSize(DIAMETER, DIAMETER); };
+ QRect buttonGeometry() const;
protected:
- bool event(QEvent *event) override;
- bool eventFilter(QObject *obj, QEvent *event) override;
+ bool event(QEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
};
diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp
index ebe0e63f..e238a4d2 100644
--- a/src/ui/InfoMessage.cpp
+++ b/src/ui/InfoMessage.cpp
@@ -19,60 +19,60 @@ constexpr int HMargin = 20;
InfoMessage::InfoMessage(QWidget *parent)
: QWidget{parent}
{
- initFont();
+ initFont();
}
InfoMessage::InfoMessage(QString msg, QWidget *parent)
: QWidget{parent}
, msg_{msg}
{
- initFont();
+ initFont();
- QFontMetrics fm{font()};
- width_ = fm.horizontalAdvance(msg_) + HPadding * 2;
- height_ = fm.ascent() + 2 * VPadding;
+ QFontMetrics fm{font()};
+ width_ = fm.horizontalAdvance(msg_) + HPadding * 2;
+ height_ = fm.ascent() + 2 * VPadding;
- setFixedHeight(height_ + 2 * HMargin);
+ setFixedHeight(height_ + 2 * HMargin);
}
void
InfoMessage::paintEvent(QPaintEvent *)
{
- QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing);
- p.setFont(font());
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing);
+ p.setFont(font());
- // Center the box horizontally & vertically.
- auto textRegion = QRectF(width() / 2 - width_ / 2, HMargin, width_, height_);
+ // Center the box horizontally & vertically.
+ auto textRegion = QRectF(width() / 2 - width_ / 2, HMargin, width_, height_);
- QPainterPath ppath;
- ppath.addRoundedRect(textRegion, height_ / 2, height_ / 2);
+ QPainterPath ppath;
+ ppath.addRoundedRect(textRegion, height_ / 2, height_ / 2);
- p.setPen(Qt::NoPen);
- p.fillPath(ppath, boxColor());
- p.drawPath(ppath);
+ p.setPen(Qt::NoPen);
+ p.fillPath(ppath, boxColor());
+ p.drawPath(ppath);
- p.setPen(QPen(textColor()));
- p.drawText(textRegion, Qt::AlignCenter, msg_);
+ p.setPen(QPen(textColor()));
+ p.drawText(textRegion, Qt::AlignCenter, msg_);
}
DateSeparator::DateSeparator(QDateTime datetime, QWidget *parent)
: InfoMessage{parent}
{
- auto now = QDateTime::currentDateTime();
+ auto now = QDateTime::currentDateTime();
- QString fmt = QLocale::system().dateFormat(QLocale::LongFormat);
+ QString fmt = QLocale::system().dateFormat(QLocale::LongFormat);
- if (now.date().year() == datetime.date().year()) {
- QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*");
- fmt = fmt.remove(rx);
- }
+ if (now.date().year() == datetime.date().year()) {
+ QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*");
+ fmt = fmt.remove(rx);
+ }
- msg_ = datetime.date().toString(fmt);
+ msg_ = datetime.date().toString(fmt);
- QFontMetrics fm{font()};
- width_ = fm.horizontalAdvance(msg_) + HPadding * 2;
- height_ = fm.ascent() + 2 * VPadding;
+ QFontMetrics fm{font()};
+ width_ = fm.horizontalAdvance(msg_) + HPadding * 2;
+ height_ = fm.ascent() + 2 * VPadding;
- setFixedHeight(height_ + 2 * HMargin);
+ setFixedHeight(height_ + 2 * HMargin);
}
diff --git a/src/ui/InfoMessage.h b/src/ui/InfoMessage.h
index cc0c57dc..486812a2 100644
--- a/src/ui/InfoMessage.h
+++ b/src/ui/InfoMessage.h
@@ -10,47 +10,47 @@
class InfoMessage : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
- Q_PROPERTY(QColor boxColor WRITE setBoxColor READ boxColor)
+ Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+ Q_PROPERTY(QColor boxColor WRITE setBoxColor READ boxColor)
public:
- explicit InfoMessage(QWidget *parent = nullptr);
- InfoMessage(QString msg, QWidget *parent = nullptr);
+ explicit InfoMessage(QWidget *parent = nullptr);
+ InfoMessage(QString msg, QWidget *parent = nullptr);
- void setTextColor(QColor color) { textColor_ = color; }
- void setBoxColor(QColor color) { boxColor_ = color; }
- void saveDatetime(QDateTime datetime) { datetime_ = datetime; }
+ void setTextColor(QColor color) { textColor_ = color; }
+ void setBoxColor(QColor color) { boxColor_ = color; }
+ void saveDatetime(QDateTime datetime) { datetime_ = datetime; }
- QColor textColor() const { return textColor_; }
- QColor boxColor() const { return boxColor_; }
- QDateTime datetime() const { return datetime_; }
+ QColor textColor() const { return textColor_; }
+ QColor boxColor() const { return boxColor_; }
+ QDateTime datetime() const { return datetime_; }
protected:
- void paintEvent(QPaintEvent *event) override;
- void initFont()
- {
- QFont f;
- f.setWeight(QFont::Medium);
- setFont(f);
- }
+ void paintEvent(QPaintEvent *event) override;
+ void initFont()
+ {
+ QFont f;
+ f.setWeight(QFont::Medium);
+ setFont(f);
+ }
- int width_;
- int height_;
+ int width_;
+ int height_;
- QString msg_;
+ QString msg_;
- QDateTime datetime_;
+ QDateTime datetime_;
- QColor textColor_ = QColor("black");
- QColor boxColor_ = QColor("white");
+ QColor textColor_ = QColor("black");
+ QColor boxColor_ = QColor("white");
};
class DateSeparator : public InfoMessage
{
- Q_OBJECT
+ Q_OBJECT
public:
- DateSeparator(QDateTime datetime, QWidget *parent = nullptr);
+ DateSeparator(QDateTime datetime, QWidget *parent = nullptr);
};
diff --git a/src/ui/Label.cpp b/src/ui/Label.cpp
index 2e8f8e1d..220fe2f0 100644
--- a/src/ui/Label.cpp
+++ b/src/ui/Label.cpp
@@ -17,16 +17,16 @@ Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f)
void
Label::mousePressEvent(QMouseEvent *e)
{
- pressPosition_ = e->pos();
- emit pressed(e);
- QLabel::mousePressEvent(e);
+ pressPosition_ = e->pos();
+ emit pressed(e);
+ QLabel::mousePressEvent(e);
}
void
Label::mouseReleaseEvent(QMouseEvent *e)
{
- emit released(e);
- if (pressPosition_ == e->pos())
- emit clicked(e);
- QLabel::mouseReleaseEvent(e);
+ emit released(e);
+ if (pressPosition_ == e->pos())
+ emit clicked(e);
+ QLabel::mouseReleaseEvent(e);
}
diff --git a/src/ui/Label.h b/src/ui/Label.h
index a3eb511b..b6e76b77 100644
--- a/src/ui/Label.h
+++ b/src/ui/Label.h
@@ -8,22 +8,22 @@
class Label : public QLabel
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit Label(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
- explicit Label(const QString &text,
- QWidget *parent = Q_NULLPTR,
- Qt::WindowFlags f = Qt::WindowFlags());
+ explicit Label(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
+ explicit Label(const QString &text,
+ QWidget *parent = Q_NULLPTR,
+ Qt::WindowFlags f = Qt::WindowFlags());
signals:
- void clicked(QMouseEvent *e);
- void pressed(QMouseEvent *e);
- void released(QMouseEvent *e);
+ void clicked(QMouseEvent *e);
+ void pressed(QMouseEvent *e);
+ void released(QMouseEvent *e);
protected:
- void mousePressEvent(QMouseEvent *e) override;
- void mouseReleaseEvent(QMouseEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
- QPoint pressPosition_;
+ QPoint pressPosition_;
};
diff --git a/src/ui/LoadingIndicator.cpp b/src/ui/LoadingIndicator.cpp
index fb3c761c..7581ec83 100644
--- a/src/ui/LoadingIndicator.cpp
+++ b/src/ui/LoadingIndicator.cpp
@@ -14,70 +14,70 @@ LoadingIndicator::LoadingIndicator(QWidget *parent)
, angle_(0)
, color_(Qt::black)
{
- setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- setFocusPolicy(Qt::NoFocus);
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ setFocusPolicy(Qt::NoFocus);
- timer_ = new QTimer(this);
- connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout()));
+ timer_ = new QTimer(this);
+ connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout()));
}
void
LoadingIndicator::paintEvent(QPaintEvent *e)
{
- Q_UNUSED(e)
+ Q_UNUSED(e)
- if (!timer_->isActive())
- return;
+ if (!timer_->isActive())
+ return;
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
- int width = qMin(this->width(), this->height());
+ int width = qMin(this->width(), this->height());
- int outerRadius = (width - 4) * 0.5f;
- int innerRadius = outerRadius * 0.78f;
+ int outerRadius = (width - 4) * 0.5f;
+ int innerRadius = outerRadius * 0.78f;
- int capsuleRadius = (outerRadius - innerRadius) / 2;
+ int capsuleRadius = (outerRadius - innerRadius) / 2;
- for (int i = 0; i < 8; ++i) {
- QColor color = color_;
+ for (int i = 0; i < 8; ++i) {
+ QColor color = color_;
- color.setAlphaF(1.0f - (i / 8.0f));
+ color.setAlphaF(1.0f - (i / 8.0f));
- painter.setPen(Qt::NoPen);
- painter.setBrush(color);
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(color);
- qreal radius = capsuleRadius * (1.0f - (i / 16.0f));
+ qreal radius = capsuleRadius * (1.0f - (i / 16.0f));
- painter.save();
+ painter.save();
- painter.translate(rect().center());
- painter.rotate(angle_ - i * 45.0f);
+ painter.translate(rect().center());
+ painter.rotate(angle_ - i * 45.0f);
- QPointF center = QPointF(-capsuleRadius, -innerRadius);
- painter.drawEllipse(center, radius * 2, radius * 2);
+ QPointF center = QPointF(-capsuleRadius, -innerRadius);
+ painter.drawEllipse(center, radius * 2, radius * 2);
- painter.restore();
- }
+ painter.restore();
+ }
}
void
LoadingIndicator::start()
{
- timer_->start(interval_);
- show();
+ timer_->start(interval_);
+ show();
}
void
LoadingIndicator::stop()
{
- timer_->stop();
- hide();
+ timer_->stop();
+ hide();
}
void
LoadingIndicator::onTimeout()
{
- angle_ = (angle_ + 45) % 360;
- repaint();
+ angle_ = (angle_ + 45) % 360;
+ repaint();
}
diff --git a/src/ui/LoadingIndicator.h b/src/ui/LoadingIndicator.h
index ba56b449..458ecd40 100644
--- a/src/ui/LoadingIndicator.h
+++ b/src/ui/LoadingIndicator.h
@@ -12,30 +12,30 @@ class QTimer;
class QPaintEvent;
class LoadingIndicator : public QWidget
{
- Q_OBJECT
- Q_PROPERTY(QColor color READ color WRITE setColor)
+ Q_OBJECT
+ Q_PROPERTY(QColor color READ color WRITE setColor)
public:
- LoadingIndicator(QWidget *parent = nullptr);
+ LoadingIndicator(QWidget *parent = nullptr);
- void paintEvent(QPaintEvent *e) override;
+ void paintEvent(QPaintEvent *e) override;
- void start();
- void stop();
+ void start();
+ void stop();
- QColor color() { return color_; }
- void setColor(QColor color) { color_ = color; }
+ QColor color() { return color_; }
+ void setColor(QColor color) { color_ = color; }
- int interval() { return interval_; }
- void setInterval(int interval) { interval_ = interval; }
+ int interval() { return interval_; }
+ void setInterval(int interval) { interval_ = interval; }
private slots:
- void onTimeout();
+ void onTimeout();
private:
- int interval_;
- int angle_;
+ int interval_;
+ int angle_;
- QColor color_;
- QTimer *timer_;
+ QColor color_;
+ QTimer *timer_;
};
diff --git a/src/ui/Menu.h b/src/ui/Menu.h
index fd2946dd..d1ac2b80 100644
--- a/src/ui/Menu.h
+++ b/src/ui/Menu.h
@@ -10,16 +10,16 @@
class Menu : public QMenu
{
- Q_OBJECT
+ Q_OBJECT
public:
- Menu(QWidget *parent = nullptr)
- : QMenu(parent){};
+ Menu(QWidget *parent = nullptr)
+ : QMenu(parent){};
protected:
- void leaveEvent(QEvent *e) override
- {
- hide();
+ void leaveEvent(QEvent *e) override
+ {
+ hide();
- QMenu::leaveEvent(e);
- }
+ QMenu::leaveEvent(e);
+ }
};
diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp
index c691bab0..72758653 100644
--- a/src/ui/MxcAnimatedImage.cpp
+++ b/src/ui/MxcAnimatedImage.cpp
@@ -19,160 +19,157 @@
void
MxcAnimatedImage::startDownload()
{
- if (!room_)
- return;
- if (eventId_.isEmpty())
- return;
-
- auto event = room_->eventById(eventId_);
- if (!event) {
- nhlog::ui()->error("Failed to load media for event {}, event not found.",
- eventId_.toStdString());
- return;
- }
+ if (!room_)
+ return;
+ if (eventId_.isEmpty())
+ return;
- QByteArray mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)).toUtf8();
+ auto event = room_->eventById(eventId_);
+ if (!event) {
+ nhlog::ui()->error("Failed to load media for event {}, event not found.",
+ eventId_.toStdString());
+ return;
+ }
- animatable_ = QMovie::supportedFormats().contains(mimeType.split('/').back());
- animatableChanged();
+ QByteArray mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)).toUtf8();
- if (!animatable_)
- return;
+ animatable_ = QMovie::supportedFormats().contains(mimeType.split('/').back());
+ animatableChanged();
- QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
- QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
+ if (!animatable_)
+ return;
- auto encryptionInfo = mtx::accessors::file(*event);
+ QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
+ QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
- // If the message is a link to a non mxcUrl, don't download it
- if (!mxcUrl.startsWith("mxc://")) {
- return;
- }
+ auto encryptionInfo = mtx::accessors::file(*event);
- QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
-
- const auto url = mxcUrl.toStdString();
- const auto name = QString(mxcUrl).remove("mxc://");
- QFileInfo filename(QString("%1/media_cache/media/%2.%3")
- .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(name)
- .arg(suffix));
- if (QDir::cleanPath(name) != name) {
- nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
- return;
- }
+ // If the message is a link to a non mxcUrl, don't download it
+ if (!mxcUrl.startsWith("mxc://")) {
+ return;
+ }
+
+ QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
+
+ const auto url = mxcUrl.toStdString();
+ const auto name = QString(mxcUrl).remove("mxc://");
+ QFileInfo filename(QString("%1/media_cache/media/%2.%3")
+ .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
+ .arg(name)
+ .arg(suffix));
+ if (QDir::cleanPath(name) != name) {
+ nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
+ return;
+ }
+
+ QDir().mkpath(filename.path());
+
+ QPointer<MxcAnimatedImage> self = this;
+
+ auto processBuffer = [this, mimeType, encryptionInfo, self](QIODevice &device) {
+ if (!self)
+ return;
- QDir().mkpath(filename.path());
-
- QPointer<MxcAnimatedImage> self = this;
-
- auto processBuffer = [this, mimeType, encryptionInfo, self](QIODevice &device) {
- if (!self)
- return;
-
- if (buffer.isOpen()) {
- movie.stop();
- movie.setDevice(nullptr);
- buffer.close();
- }
-
- if (encryptionInfo) {
- QByteArray ba = device.readAll();
- std::string temp(ba.constData(), ba.size());
- temp = mtx::crypto::to_string(
- mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
- buffer.setData(temp.data(), temp.size());
- } else {
- buffer.setData(device.readAll());
- }
- buffer.open(QIODevice::ReadOnly);
- buffer.reset();
-
- QTimer::singleShot(0, this, [this, mimeType] {
- nhlog::ui()->info("Playing movie with size: {}, {}",
- buffer.bytesAvailable(),
- buffer.isOpen());
- movie.setFormat(mimeType);
- movie.setDevice(&buffer);
- if (play_)
- movie.start();
- else
- movie.jumpToFrame(0);
- emit loadedChanged();
- update();
- });
- };
-
- if (filename.isReadable()) {
- QFile f(filename.filePath());
- if (f.open(QIODevice::ReadOnly)) {
- processBuffer(f);
- return;
- }
+ if (buffer.isOpen()) {
+ movie.stop();
+ movie.setDevice(nullptr);
+ buffer.close();
}
- http::client()->download(
- url,
- [filename, url, processBuffer](const std::string &data,
- const std::string &,
- const std::string &,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve media {}: {} {}",
- url,
- err->matrix_error.error,
- static_cast<int>(err->status_code));
- return;
- }
-
- try {
- QFile file(filename.filePath());
-
- if (!file.open(QIODevice::WriteOnly))
- return;
-
- QByteArray ba(data.data(), (int)data.size());
- file.write(ba);
- file.close();
-
- QBuffer buf(&ba);
- buf.open(QBuffer::ReadOnly);
- processBuffer(buf);
- } catch (const std::exception &e) {
- nhlog::ui()->warn("Error while saving file to: {}", e.what());
- }
- });
+ if (encryptionInfo) {
+ QByteArray ba = device.readAll();
+ std::string temp(ba.constData(), ba.size());
+ temp = mtx::crypto::to_string(mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
+ buffer.setData(temp.data(), temp.size());
+ } else {
+ buffer.setData(device.readAll());
+ }
+ buffer.open(QIODevice::ReadOnly);
+ buffer.reset();
+
+ QTimer::singleShot(0, this, [this, mimeType] {
+ nhlog::ui()->info(
+ "Playing movie with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
+ movie.setFormat(mimeType);
+ movie.setDevice(&buffer);
+ if (play_)
+ movie.start();
+ else
+ movie.jumpToFrame(0);
+ emit loadedChanged();
+ update();
+ });
+ };
+
+ if (filename.isReadable()) {
+ QFile f(filename.filePath());
+ if (f.open(QIODevice::ReadOnly)) {
+ processBuffer(f);
+ return;
+ }
+ }
+
+ http::client()->download(url,
+ [filename, url, processBuffer](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve media {}: {} {}",
+ url,
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ try {
+ QFile file(filename.filePath());
+
+ if (!file.open(QIODevice::WriteOnly))
+ return;
+
+ QByteArray ba(data.data(), (int)data.size());
+ file.write(ba);
+ file.close();
+
+ QBuffer buf(&ba);
+ buf.open(QBuffer::ReadOnly);
+ processBuffer(buf);
+ } catch (const std::exception &e) {
+ nhlog::ui()->warn("Error while saving file to: {}", e.what());
+ }
+ });
}
QSGNode *
MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
- if (!imageDirty)
- return oldNode;
-
- imageDirty = false;
- QSGImageNode *n = static_cast<QSGImageNode *>(oldNode);
- if (!n) {
- n = window()->createImageNode();
- n->setOwnsTexture(true);
- // n->setFlags(QSGNode::OwnedByParent | QSGNode::OwnsGeometry |
- // GSGNode::OwnsMaterial);
- n->setFlags(QSGNode::OwnedByParent);
- }
-
- // n->setTexture(nullptr);
- auto img = movie.currentImage();
- if (!img.isNull())
- n->setTexture(window()->createTextureFromImage(img));
- else {
- delete n;
- return nullptr;
- }
-
- n->setSourceRect(img.rect());
- n->setRect(QRect(0, 0, width(), height()));
- n->setFiltering(QSGTexture::Linear);
- n->setMipmapFiltering(QSGTexture::Linear);
-
- return n;
+ if (!imageDirty)
+ return oldNode;
+
+ imageDirty = false;
+ QSGImageNode *n = static_cast<QSGImageNode *>(oldNode);
+ if (!n) {
+ n = window()->createImageNode();
+ n->setOwnsTexture(true);
+ // n->setFlags(QSGNode::OwnedByParent | QSGNode::OwnsGeometry |
+ // GSGNode::OwnsMaterial);
+ n->setFlags(QSGNode::OwnedByParent);
+ }
+
+ // n->setTexture(nullptr);
+ auto img = movie.currentImage();
+ if (!img.isNull())
+ n->setTexture(window()->createTextureFromImage(img));
+ else {
+ delete n;
+ return nullptr;
+ }
+
+ n->setSourceRect(img.rect());
+ n->setRect(QRect(0, 0, width(), height()));
+ n->setFiltering(QSGTexture::Linear);
+ n->setMipmapFiltering(QSGTexture::Linear);
+
+ return n;
}
diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h
index 2e0489ad..c3ca24d1 100644
--- a/src/ui/MxcAnimatedImage.h
+++ b/src/ui/MxcAnimatedImage.h
@@ -14,78 +14,78 @@ class TimelineModel;
// This is an AnimatedImage, that can draw encrypted images
class MxcAnimatedImage : public QQuickItem
{
- Q_OBJECT
- Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED)
- Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
- Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged)
- Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
- Q_PROPERTY(bool play READ play WRITE setPlay NOTIFY playChanged)
+ Q_OBJECT
+ Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED)
+ Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
+ Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged)
+ Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
+ Q_PROPERTY(bool play READ play WRITE setPlay NOTIFY playChanged)
public:
- MxcAnimatedImage(QQuickItem *parent = nullptr)
- : QQuickItem(parent)
- {
- connect(this, &MxcAnimatedImage::eventIdChanged, &MxcAnimatedImage::startDownload);
- connect(this, &MxcAnimatedImage::roomChanged, &MxcAnimatedImage::startDownload);
- connect(&movie, &QMovie::frameChanged, this, &MxcAnimatedImage::newFrame);
- setFlag(QQuickItem::ItemHasContents);
- // setAcceptHoverEvents(true);
- }
+ MxcAnimatedImage(QQuickItem *parent = nullptr)
+ : QQuickItem(parent)
+ {
+ connect(this, &MxcAnimatedImage::eventIdChanged, &MxcAnimatedImage::startDownload);
+ connect(this, &MxcAnimatedImage::roomChanged, &MxcAnimatedImage::startDownload);
+ connect(&movie, &QMovie::frameChanged, this, &MxcAnimatedImage::newFrame);
+ setFlag(QQuickItem::ItemHasContents);
+ // setAcceptHoverEvents(true);
+ }
- bool animatable() const { return animatable_; }
- bool loaded() const { return buffer.size() > 0; }
- bool play() const { return play_; }
- QString eventId() const { return eventId_; }
- TimelineModel *room() const { return room_; }
- void setEventId(QString newEventId)
- {
- if (eventId_ != newEventId) {
- eventId_ = newEventId;
- emit eventIdChanged();
- }
+ bool animatable() const { return animatable_; }
+ bool loaded() const { return buffer.size() > 0; }
+ bool play() const { return play_; }
+ QString eventId() const { return eventId_; }
+ TimelineModel *room() const { return room_; }
+ void setEventId(QString newEventId)
+ {
+ if (eventId_ != newEventId) {
+ eventId_ = newEventId;
+ emit eventIdChanged();
}
- void setRoom(TimelineModel *room)
- {
- if (room_ != room) {
- room_ = room;
- emit roomChanged();
- }
+ }
+ void setRoom(TimelineModel *room)
+ {
+ if (room_ != room) {
+ room_ = room;
+ emit roomChanged();
}
- void setPlay(bool newPlay)
- {
- if (play_ != newPlay) {
- play_ = newPlay;
- movie.setPaused(!play_);
- emit playChanged();
- }
+ }
+ void setPlay(bool newPlay)
+ {
+ if (play_ != newPlay) {
+ play_ = newPlay;
+ movie.setPaused(!play_);
+ emit playChanged();
}
+ }
- QSGNode *updatePaintNode(QSGNode *oldNode,
- QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
+ QSGNode *updatePaintNode(QSGNode *oldNode,
+ QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
signals:
- void roomChanged();
- void eventIdChanged();
- void animatableChanged();
- void loadedChanged();
- void playChanged();
+ void roomChanged();
+ void eventIdChanged();
+ void animatableChanged();
+ void loadedChanged();
+ void playChanged();
private slots:
- void startDownload();
- void newFrame(int frame)
- {
- currentFrame = frame;
- imageDirty = true;
- update();
- }
+ void startDownload();
+ void newFrame(int frame)
+ {
+ currentFrame = frame;
+ imageDirty = true;
+ update();
+ }
private:
- TimelineModel *room_ = nullptr;
- QString eventId_;
- QString filename_;
- bool animatable_ = false;
- QBuffer buffer;
- QMovie movie;
- int currentFrame = 0;
- bool imageDirty = true;
- bool play_ = true;
+ TimelineModel *room_ = nullptr;
+ QString eventId_;
+ QString filename_;
+ bool animatable_ = false;
+ QBuffer buffer;
+ QMovie movie;
+ int currentFrame = 0;
+ bool imageDirty = true;
+ bool play_ = true;
};
diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp
index dc65de7c..db8c0f1f 100644
--- a/src/ui/MxcMediaProxy.cpp
+++ b/src/ui/MxcMediaProxy.cpp
@@ -21,122 +21,119 @@
void
MxcMediaProxy::setVideoSurface(QAbstractVideoSurface *surface)
{
- qDebug() << "Changing surface";
- m_surface = surface;
- setVideoOutput(m_surface);
+ qDebug() << "Changing surface";
+ m_surface = surface;
+ setVideoOutput(m_surface);
}
QAbstractVideoSurface *
MxcMediaProxy::getVideoSurface()
{
- return m_surface;
+ return m_surface;
}
void
MxcMediaProxy::startDownload()
{
- if (!room_)
- return;
- if (eventId_.isEmpty())
- return;
-
- auto event = room_->eventById(eventId_);
- if (!event) {
- nhlog::ui()->error("Failed to load media for event {}, event not found.",
- eventId_.toStdString());
- return;
+ if (!room_)
+ return;
+ if (eventId_.isEmpty())
+ return;
+
+ auto event = room_->eventById(eventId_);
+ if (!event) {
+ nhlog::ui()->error("Failed to load media for event {}, event not found.",
+ eventId_.toStdString());
+ return;
+ }
+
+ QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
+ QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
+ QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
+
+ auto encryptionInfo = mtx::accessors::file(*event);
+
+ // If the message is a link to a non mxcUrl, don't download it
+ if (!mxcUrl.startsWith("mxc://")) {
+ return;
+ }
+
+ QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
+
+ const auto url = mxcUrl.toStdString();
+ const auto name = QString(mxcUrl).remove("mxc://");
+ QFileInfo filename(QString("%1/media_cache/media/%2.%3")
+ .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
+ .arg(name)
+ .arg(suffix));
+ if (QDir::cleanPath(name) != name) {
+ nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
+ return;
+ }
+
+ QDir().mkpath(filename.path());
+
+ QPointer<MxcMediaProxy> self = this;
+
+ auto processBuffer = [this, encryptionInfo, filename, self](QIODevice &device) {
+ if (!self)
+ return;
+
+ if (encryptionInfo) {
+ QByteArray ba = device.readAll();
+ std::string temp(ba.constData(), ba.size());
+ temp = mtx::crypto::to_string(mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
+ buffer.setData(temp.data(), temp.size());
+ } else {
+ buffer.setData(device.readAll());
}
-
- QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
- QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
- QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
-
- auto encryptionInfo = mtx::accessors::file(*event);
-
- // If the message is a link to a non mxcUrl, don't download it
- if (!mxcUrl.startsWith("mxc://")) {
- return;
- }
-
- QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
-
- const auto url = mxcUrl.toStdString();
- const auto name = QString(mxcUrl).remove("mxc://");
- QFileInfo filename(QString("%1/media_cache/media/%2.%3")
- .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(name)
- .arg(suffix));
- if (QDir::cleanPath(name) != name) {
- nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
- return;
- }
-
- QDir().mkpath(filename.path());
-
- QPointer<MxcMediaProxy> self = this;
-
- auto processBuffer = [this, encryptionInfo, filename, self](QIODevice &device) {
- if (!self)
- return;
-
- if (encryptionInfo) {
- QByteArray ba = device.readAll();
- std::string temp(ba.constData(), ba.size());
- temp = mtx::crypto::to_string(
- mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
- buffer.setData(temp.data(), temp.size());
- } else {
- buffer.setData(device.readAll());
- }
- buffer.open(QIODevice::ReadOnly);
- buffer.reset();
-
- QTimer::singleShot(0, this, [this, filename] {
- nhlog::ui()->info("Playing buffer with size: {}, {}",
- buffer.bytesAvailable(),
- buffer.isOpen());
- this->setMedia(QMediaContent(filename.fileName()), &buffer);
- emit loadedChanged();
- });
- };
-
- if (filename.isReadable()) {
- QFile f(filename.filePath());
- if (f.open(QIODevice::ReadOnly)) {
- processBuffer(f);
- return;
- }
+ buffer.open(QIODevice::ReadOnly);
+ buffer.reset();
+
+ QTimer::singleShot(0, this, [this, filename] {
+ nhlog::ui()->info(
+ "Playing buffer with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
+ this->setMedia(QMediaContent(filename.fileName()), &buffer);
+ emit loadedChanged();
+ });
+ };
+
+ if (filename.isReadable()) {
+ QFile f(filename.filePath());
+ if (f.open(QIODevice::ReadOnly)) {
+ processBuffer(f);
+ return;
}
-
- http::client()->download(
- url,
- [filename, url, processBuffer](const std::string &data,
- const std::string &,
- const std::string &,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve media {}: {} {}",
- url,
- err->matrix_error.error,
- static_cast<int>(err->status_code));
- return;
- }
-
- try {
- QFile file(filename.filePath());
-
- if (!file.open(QIODevice::WriteOnly))
- return;
-
- QByteArray ba(data.data(), (int)data.size());
- file.write(ba);
- file.close();
-
- QBuffer buf(&ba);
- buf.open(QBuffer::ReadOnly);
- processBuffer(buf);
- } catch (const std::exception &e) {
- nhlog::ui()->warn("Error while saving file to: {}", e.what());
- }
- });
+ }
+
+ http::client()->download(url,
+ [filename, url, processBuffer](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve media {}: {} {}",
+ url,
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ try {
+ QFile file(filename.filePath());
+
+ if (!file.open(QIODevice::WriteOnly))
+ return;
+
+ QByteArray ba(data.data(), (int)data.size());
+ file.write(ba);
+ file.close();
+
+ QBuffer buf(&ba);
+ buf.open(QBuffer::ReadOnly);
+ processBuffer(buf);
+ } catch (const std::exception &e) {
+ nhlog::ui()->warn("Error while saving file to: {}", e.what());
+ }
+ });
}
diff --git a/src/ui/MxcMediaProxy.h b/src/ui/MxcMediaProxy.h
index 14541815..18152c75 100644
--- a/src/ui/MxcMediaProxy.h
+++ b/src/ui/MxcMediaProxy.h
@@ -20,61 +20,58 @@ class TimelineModel;
// need the videoSurface property, so that part is really easy!
class MxcMediaProxy : public QMediaPlayer
{
- Q_OBJECT
- Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED)
- Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
- Q_PROPERTY(QAbstractVideoSurface *videoSurface READ getVideoSurface WRITE setVideoSurface)
- Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
+ Q_OBJECT
+ Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED)
+ Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
+ Q_PROPERTY(QAbstractVideoSurface *videoSurface READ getVideoSurface WRITE setVideoSurface)
+ Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
public:
- MxcMediaProxy(QObject *parent = nullptr)
- : QMediaPlayer(parent)
- {
- connect(this, &MxcMediaProxy::eventIdChanged, &MxcMediaProxy::startDownload);
- connect(this, &MxcMediaProxy::roomChanged, &MxcMediaProxy::startDownload);
- connect(this,
- qOverload<QMediaPlayer::Error>(&MxcMediaProxy::error),
- [this](QMediaPlayer::Error error) {
- nhlog::ui()->info("Media player error {} and errorStr {}",
- error,
- this->errorString().toStdString());
- });
- connect(this,
- &MxcMediaProxy::mediaStatusChanged,
- [this](QMediaPlayer::MediaStatus status) {
- nhlog::ui()->info(
- "Media player status {} and error {}", status, this->error());
- });
- }
+ MxcMediaProxy(QObject *parent = nullptr)
+ : QMediaPlayer(parent)
+ {
+ connect(this, &MxcMediaProxy::eventIdChanged, &MxcMediaProxy::startDownload);
+ connect(this, &MxcMediaProxy::roomChanged, &MxcMediaProxy::startDownload);
+ connect(this,
+ qOverload<QMediaPlayer::Error>(&MxcMediaProxy::error),
+ [this](QMediaPlayer::Error error) {
+ nhlog::ui()->info("Media player error {} and errorStr {}",
+ error,
+ this->errorString().toStdString());
+ });
+ connect(this, &MxcMediaProxy::mediaStatusChanged, [this](QMediaPlayer::MediaStatus status) {
+ nhlog::ui()->info("Media player status {} and error {}", status, this->error());
+ });
+ }
- bool loaded() const { return buffer.size() > 0; }
- QString eventId() const { return eventId_; }
- TimelineModel *room() const { return room_; }
- void setEventId(QString newEventId)
- {
- eventId_ = newEventId;
- emit eventIdChanged();
- }
- void setRoom(TimelineModel *room)
- {
- room_ = room;
- emit roomChanged();
- }
- void setVideoSurface(QAbstractVideoSurface *surface);
- QAbstractVideoSurface *getVideoSurface();
+ bool loaded() const { return buffer.size() > 0; }
+ QString eventId() const { return eventId_; }
+ TimelineModel *room() const { return room_; }
+ void setEventId(QString newEventId)
+ {
+ eventId_ = newEventId;
+ emit eventIdChanged();
+ }
+ void setRoom(TimelineModel *room)
+ {
+ room_ = room;
+ emit roomChanged();
+ }
+ void setVideoSurface(QAbstractVideoSurface *surface);
+ QAbstractVideoSurface *getVideoSurface();
signals:
- void roomChanged();
- void eventIdChanged();
- void loadedChanged();
- void newBuffer(QMediaContent, QIODevice *buf);
+ void roomChanged();
+ void eventIdChanged();
+ void loadedChanged();
+ void newBuffer(QMediaContent, QIODevice *buf);
private slots:
- void startDownload();
+ void startDownload();
private:
- TimelineModel *room_ = nullptr;
- QString eventId_;
- QString filename_;
- QBuffer buffer;
- QAbstractVideoSurface *m_surface = nullptr;
+ TimelineModel *room_ = nullptr;
+ QString eventId_;
+ QString filename_;
+ QBuffer buffer;
+ QAbstractVideoSurface *m_surface = nullptr;
};
diff --git a/src/ui/NhekoCursorShape.cpp b/src/ui/NhekoCursorShape.cpp
index b36eedbd..70991757 100644
--- a/src/ui/NhekoCursorShape.cpp
+++ b/src/ui/NhekoCursorShape.cpp
@@ -14,16 +14,16 @@ NhekoCursorShape::NhekoCursorShape(QQuickItem *parent)
Qt::CursorShape
NhekoCursorShape::cursorShape() const
{
- return cursor().shape();
+ return cursor().shape();
}
void
NhekoCursorShape::setCursorShape(Qt::CursorShape cursorShape)
{
- if (currentShape_ == cursorShape)
- return;
+ if (currentShape_ == cursorShape)
+ return;
- currentShape_ = cursorShape;
- setCursor(cursorShape);
- emit cursorShapeChanged();
+ currentShape_ = cursorShape;
+ setCursor(cursorShape);
+ emit cursorShapeChanged();
}
diff --git a/src/ui/NhekoCursorShape.h b/src/ui/NhekoCursorShape.h
index 6f6a2b82..b3a0a1ba 100644
--- a/src/ui/NhekoCursorShape.h
+++ b/src/ui/NhekoCursorShape.h
@@ -11,20 +11,20 @@
class NhekoCursorShape : public QQuickItem
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape NOTIFY
- cursorShapeChanged)
+ Q_PROPERTY(
+ Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape NOTIFY cursorShapeChanged)
public:
- explicit NhekoCursorShape(QQuickItem *parent = 0);
+ explicit NhekoCursorShape(QQuickItem *parent = 0);
private:
- Qt::CursorShape cursorShape() const;
- void setCursorShape(Qt::CursorShape cursorShape);
+ Qt::CursorShape cursorShape() const;
+ void setCursorShape(Qt::CursorShape cursorShape);
- Qt::CursorShape currentShape_;
+ Qt::CursorShape currentShape_;
signals:
- void cursorShapeChanged();
+ void cursorShapeChanged();
};
diff --git a/src/ui/NhekoDropArea.cpp b/src/ui/NhekoDropArea.cpp
index bbcedd7e..b1b53c3d 100644
--- a/src/ui/NhekoDropArea.cpp
+++ b/src/ui/NhekoDropArea.cpp
@@ -16,28 +16,28 @@
NhekoDropArea::NhekoDropArea(QQuickItem *parent)
: QQuickItem(parent)
{
- setFlags(ItemAcceptsDrops);
+ setFlags(ItemAcceptsDrops);
}
void
NhekoDropArea::dragEnterEvent(QDragEnterEvent *event)
{
- event->acceptProposedAction();
+ event->acceptProposedAction();
}
void
NhekoDropArea::dragMoveEvent(QDragMoveEvent *event)
{
- event->acceptProposedAction();
+ event->acceptProposedAction();
}
void
NhekoDropArea::dropEvent(QDropEvent *event)
{
- if (event) {
- auto model = ChatPage::instance()->timelineManager()->rooms()->getRoomById(roomid_);
- if (model) {
- model->input()->insertMimeData(event->mimeData());
- }
+ if (event) {
+ auto model = ChatPage::instance()->timelineManager()->rooms()->getRoomById(roomid_);
+ if (model) {
+ model->input()->insertMimeData(event->mimeData());
}
+ }
}
diff --git a/src/ui/NhekoDropArea.h b/src/ui/NhekoDropArea.h
index 9fbf1737..7b3de5c9 100644
--- a/src/ui/NhekoDropArea.h
+++ b/src/ui/NhekoDropArea.h
@@ -6,29 +6,29 @@
class NhekoDropArea : public QQuickItem
{
- Q_OBJECT
- Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged)
+ Q_OBJECT
+ Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged)
public:
- NhekoDropArea(QQuickItem *parent = nullptr);
+ NhekoDropArea(QQuickItem *parent = nullptr);
signals:
- void roomidChanged(QString roomid);
+ void roomidChanged(QString roomid);
public slots:
- void setRoomid(QString roomid)
- {
- if (roomid_ != roomid) {
- roomid_ = roomid;
- emit roomidChanged(roomid);
- }
+ void setRoomid(QString roomid)
+ {
+ if (roomid_ != roomid) {
+ roomid_ = roomid;
+ emit roomidChanged(roomid);
}
- QString roomid() const { return roomid_; }
+ }
+ QString roomid() const { return roomid_; }
protected:
- void dragEnterEvent(QDragEnterEvent *event) override;
- void dragMoveEvent(QDragMoveEvent *event) override;
- void dropEvent(QDropEvent *event) override;
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void dragMoveEvent(QDragMoveEvent *event) override;
+ void dropEvent(QDropEvent *event) override;
private:
- QString roomid_;
+ QString roomid_;
};
diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp
index 355f187b..d6824996 100644
--- a/src/ui/NhekoGlobalObject.cpp
+++ b/src/ui/NhekoGlobalObject.cpp
@@ -17,93 +17,93 @@
Nheko::Nheko()
{
- connect(
- UserSettings::instance().get(), &UserSettings::themeChanged, this, &Nheko::colorsChanged);
- connect(ChatPage::instance(), &ChatPage::contentLoaded, this, &Nheko::updateUserProfile);
+ connect(
+ UserSettings::instance().get(), &UserSettings::themeChanged, this, &Nheko::colorsChanged);
+ connect(ChatPage::instance(), &ChatPage::contentLoaded, this, &Nheko::updateUserProfile);
}
void
Nheko::updateUserProfile()
{
- if (cache::client() && cache::client()->isInitialized())
- currentUser_.reset(
- new UserProfile("", utils::localUser(), ChatPage::instance()->timelineManager()));
- else
- currentUser_.reset();
- emit profileChanged();
+ if (cache::client() && cache::client()->isInitialized())
+ currentUser_.reset(
+ new UserProfile("", utils::localUser(), ChatPage::instance()->timelineManager()));
+ else
+ currentUser_.reset();
+ emit profileChanged();
}
QPalette
Nheko::colors() const
{
- return Theme::paletteFromTheme(UserSettings::instance()->theme().toStdString());
+ return Theme::paletteFromTheme(UserSettings::instance()->theme().toStdString());
}
QPalette
Nheko::inactiveColors() const
{
- auto p = colors();
- p.setCurrentColorGroup(QPalette::ColorGroup::Inactive);
- return p;
+ auto p = colors();
+ p.setCurrentColorGroup(QPalette::ColorGroup::Inactive);
+ return p;
}
Theme
Nheko::theme() const
{
- return Theme(UserSettings::instance()->theme().toStdString());
+ return Theme(UserSettings::instance()->theme().toStdString());
}
void
Nheko::openLink(QString link) const
{
- QUrl url(link);
- // Open externally if we couldn't handle it internally
- if (!ChatPage::instance()->handleMatrixUri(url)) {
- QDesktopServices::openUrl(url);
- }
+ QUrl url(link);
+ // Open externally if we couldn't handle it internally
+ if (!ChatPage::instance()->handleMatrixUri(url)) {
+ QDesktopServices::openUrl(url);
+ }
}
void
Nheko::setStatusMessage(QString msg) const
{
- ChatPage::instance()->setStatus(msg);
+ ChatPage::instance()->setStatus(msg);
}
UserProfile *
Nheko::currentUser() const
{
- nhlog::ui()->debug("Profile requested");
+ nhlog::ui()->debug("Profile requested");
- return currentUser_.get();
+ return currentUser_.get();
}
void
Nheko::showUserSettingsPage() const
{
- ChatPage::instance()->showUserSettingsPage();
+ ChatPage::instance()->showUserSettingsPage();
}
void
Nheko::openLogoutDialog() const
{
- MainWindow::instance()->openLogoutDialog();
+ MainWindow::instance()->openLogoutDialog();
}
void
Nheko::openCreateRoomDialog() const
{
- MainWindow::instance()->openCreateRoomDialog(
- [](const mtx::requests::CreateRoom &req) { ChatPage::instance()->createRoom(req); });
+ MainWindow::instance()->openCreateRoomDialog(
+ [](const mtx::requests::CreateRoom &req) { ChatPage::instance()->createRoom(req); });
}
void
Nheko::openJoinRoomDialog() const
{
- MainWindow::instance()->openJoinRoomDialog(
- [](const QString &room_id) { ChatPage::instance()->joinRoom(room_id); });
+ MainWindow::instance()->openJoinRoomDialog(
+ [](const QString &room_id) { ChatPage::instance()->joinRoom(room_id); });
}
void
Nheko::reparent(QWindow *win) const
{
- win->setTransientParent(MainWindow::instance()->windowHandle());
+ win->setTransientParent(MainWindow::instance()->windowHandle());
}
diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h
index d4d119dc..aa8435d1 100644
--- a/src/ui/NhekoGlobalObject.h
+++ b/src/ui/NhekoGlobalObject.h
@@ -15,51 +15,51 @@ class QWindow;
class Nheko : public QObject
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QPalette colors READ colors NOTIFY colorsChanged)
- Q_PROPERTY(QPalette inactiveColors READ inactiveColors NOTIFY colorsChanged)
- Q_PROPERTY(Theme theme READ theme NOTIFY colorsChanged)
- Q_PROPERTY(int avatarSize READ avatarSize CONSTANT)
- Q_PROPERTY(int paddingSmall READ paddingSmall CONSTANT)
- Q_PROPERTY(int paddingMedium READ paddingMedium CONSTANT)
- Q_PROPERTY(int paddingLarge READ paddingLarge CONSTANT)
+ Q_PROPERTY(QPalette colors READ colors NOTIFY colorsChanged)
+ Q_PROPERTY(QPalette inactiveColors READ inactiveColors NOTIFY colorsChanged)
+ Q_PROPERTY(Theme theme READ theme NOTIFY colorsChanged)
+ Q_PROPERTY(int avatarSize READ avatarSize CONSTANT)
+ Q_PROPERTY(int paddingSmall READ paddingSmall CONSTANT)
+ Q_PROPERTY(int paddingMedium READ paddingMedium CONSTANT)
+ Q_PROPERTY(int paddingLarge READ paddingLarge CONSTANT)
- Q_PROPERTY(UserProfile *currentUser READ currentUser NOTIFY profileChanged)
+ Q_PROPERTY(UserProfile *currentUser READ currentUser NOTIFY profileChanged)
public:
- Nheko();
+ Nheko();
- QPalette colors() const;
- QPalette inactiveColors() const;
- Theme theme() const;
+ QPalette colors() const;
+ QPalette inactiveColors() const;
+ Theme theme() const;
- int avatarSize() const { return 40; }
+ int avatarSize() const { return 40; }
- int paddingSmall() const { return 4; }
- int paddingMedium() const { return 8; }
- int paddingLarge() const { return 20; }
- UserProfile *currentUser() const;
+ int paddingSmall() const { return 4; }
+ int paddingMedium() const { return 8; }
+ int paddingLarge() const { return 20; }
+ UserProfile *currentUser() const;
- Q_INVOKABLE QFont monospaceFont() const
- {
- return QFontDatabase::systemFont(QFontDatabase::FixedFont);
- }
- Q_INVOKABLE void openLink(QString link) const;
- Q_INVOKABLE void setStatusMessage(QString msg) const;
- Q_INVOKABLE void showUserSettingsPage() const;
- Q_INVOKABLE void openLogoutDialog() const;
- Q_INVOKABLE void openCreateRoomDialog() const;
- Q_INVOKABLE void openJoinRoomDialog() const;
- Q_INVOKABLE void reparent(QWindow *win) const;
+ Q_INVOKABLE QFont monospaceFont() const
+ {
+ return QFontDatabase::systemFont(QFontDatabase::FixedFont);
+ }
+ Q_INVOKABLE void openLink(QString link) const;
+ Q_INVOKABLE void setStatusMessage(QString msg) const;
+ Q_INVOKABLE void showUserSettingsPage() const;
+ Q_INVOKABLE void openLogoutDialog() const;
+ Q_INVOKABLE void openCreateRoomDialog() const;
+ Q_INVOKABLE void openJoinRoomDialog() const;
+ Q_INVOKABLE void reparent(QWindow *win) const;
public slots:
- void updateUserProfile();
+ void updateUserProfile();
signals:
- void colorsChanged();
- void profileChanged();
+ void colorsChanged();
+ void profileChanged();
private:
- QScopedPointer<UserProfile> currentUser_;
+ QScopedPointer<UserProfile> currentUser_;
};
diff --git a/src/ui/OverlayModal.cpp b/src/ui/OverlayModal.cpp
index f5f28732..6534c4bc 100644
--- a/src/ui/OverlayModal.cpp
+++ b/src/ui/OverlayModal.cpp
@@ -12,50 +12,50 @@ OverlayModal::OverlayModal(QWidget *parent)
: OverlayWidget(parent)
, color_{QColor(30, 30, 30, 170)}
{
- layout_ = new QVBoxLayout(this);
- layout_->setSpacing(0);
- layout_->setContentsMargins(10, 40, 10, 20);
- setContentAlignment(Qt::AlignCenter);
+ layout_ = new QVBoxLayout(this);
+ layout_->setSpacing(0);
+ layout_->setContentsMargins(10, 40, 10, 20);
+ setContentAlignment(Qt::AlignCenter);
}
void
OverlayModal::setWidget(QWidget *widget)
{
- // Delete the previous widget
- if (layout_->count() > 0) {
- QLayoutItem *item;
- while ((item = layout_->takeAt(0)) != nullptr) {
- delete item->widget();
- delete item;
- }
+ // Delete the previous widget
+ if (layout_->count() > 0) {
+ QLayoutItem *item;
+ while ((item = layout_->takeAt(0)) != nullptr) {
+ delete item->widget();
+ delete item;
}
+ }
- layout_->addWidget(widget);
- content_ = widget;
- content_->setFocus();
+ layout_->addWidget(widget);
+ content_ = widget;
+ content_->setFocus();
}
void
OverlayModal::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
- QPainter painter(this);
- painter.fillRect(rect(), color_);
+ QPainter painter(this);
+ painter.fillRect(rect(), color_);
}
void
OverlayModal::mousePressEvent(QMouseEvent *e)
{
- if (isDismissible_ && content_ && !content_->geometry().contains(e->pos()))
- hide();
+ if (isDismissible_ && content_ && !content_->geometry().contains(e->pos()))
+ hide();
}
void
OverlayModal::keyPressEvent(QKeyEvent *event)
{
- if (event->key() == Qt::Key_Escape) {
- event->accept();
- hide();
- }
+ if (event->key() == Qt::Key_Escape) {
+ event->accept();
+ hide();
+ }
}
diff --git a/src/ui/OverlayModal.h b/src/ui/OverlayModal.h
index 005614fa..5c15f17f 100644
--- a/src/ui/OverlayModal.h
+++ b/src/ui/OverlayModal.h
@@ -15,25 +15,25 @@
class OverlayModal : public OverlayWidget
{
public:
- OverlayModal(QWidget *parent);
+ OverlayModal(QWidget *parent);
- void setColor(QColor color) { color_ = color; }
- void setDismissible(bool state) { isDismissible_ = state; }
+ void setColor(QColor color) { color_ = color; }
+ void setDismissible(bool state) { isDismissible_ = state; }
- void setContentAlignment(QFlags<Qt::AlignmentFlag> flag) { layout_->setAlignment(flag); }
- void setWidget(QWidget *widget);
+ void setContentAlignment(QFlags<Qt::AlignmentFlag> flag) { layout_->setAlignment(flag); }
+ void setWidget(QWidget *widget);
protected:
- void paintEvent(QPaintEvent *event) override;
- void keyPressEvent(QKeyEvent *event) override;
- void mousePressEvent(QMouseEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
private:
- QWidget *content_;
- QVBoxLayout *layout_;
+ QWidget *content_;
+ QVBoxLayout *layout_;
- QColor color_;
+ QColor color_;
- //! Decides whether or not the modal can be removed by clicking into it.
- bool isDismissible_ = true;
+ //! Decides whether or not the modal can be removed by clicking into it.
+ bool isDismissible_ = true;
};
diff --git a/src/ui/OverlayWidget.cpp b/src/ui/OverlayWidget.cpp
index c8c95581..4e338753 100644
--- a/src/ui/OverlayWidget.cpp
+++ b/src/ui/OverlayWidget.cpp
@@ -10,69 +10,69 @@
OverlayWidget::OverlayWidget(QWidget *parent)
: QWidget(parent)
{
- if (parent) {
- parent->installEventFilter(this);
- setGeometry(overlayGeometry());
- raise();
- }
+ if (parent) {
+ parent->installEventFilter(this);
+ setGeometry(overlayGeometry());
+ raise();
+ }
}
bool
OverlayWidget::event(QEvent *event)
{
- if (!parent())
- return QWidget::event(event);
+ if (!parent())
+ return QWidget::event(event);
- switch (event->type()) {
- case QEvent::ParentChange: {
- parent()->installEventFilter(this);
- setGeometry(overlayGeometry());
- break;
- }
- case QEvent::ParentAboutToChange: {
- parent()->removeEventFilter(this);
- break;
- }
- default:
- break;
- }
+ switch (event->type()) {
+ case QEvent::ParentChange: {
+ parent()->installEventFilter(this);
+ setGeometry(overlayGeometry());
+ break;
+ }
+ case QEvent::ParentAboutToChange: {
+ parent()->removeEventFilter(this);
+ break;
+ }
+ default:
+ break;
+ }
- return QWidget::event(event);
+ return QWidget::event(event);
}
bool
OverlayWidget::eventFilter(QObject *obj, QEvent *event)
{
- switch (event->type()) {
- case QEvent::Move:
- case QEvent::Resize:
- setGeometry(overlayGeometry());
- break;
- default:
- break;
- }
+ switch (event->type()) {
+ case QEvent::Move:
+ case QEvent::Resize:
+ setGeometry(overlayGeometry());
+ break;
+ default:
+ break;
+ }
- return QWidget::eventFilter(obj, event);
+ return QWidget::eventFilter(obj, event);
}
QRect
OverlayWidget::overlayGeometry() const
{
- QWidget *widget = parentWidget();
+ QWidget *widget = parentWidget();
- if (!widget)
- return QRect();
+ if (!widget)
+ return QRect();
- return widget->rect();
+ return widget->rect();
}
void
OverlayWidget::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
- 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/ui/OverlayWidget.h b/src/ui/OverlayWidget.h
index 05bb8696..5f023e35 100644
--- a/src/ui/OverlayWidget.h
+++ b/src/ui/OverlayWidget.h
@@ -11,15 +11,15 @@ class QPainter;
class OverlayWidget : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit OverlayWidget(QWidget *parent = nullptr);
+ explicit OverlayWidget(QWidget *parent = nullptr);
protected:
- bool event(QEvent *event) override;
- bool eventFilter(QObject *obj, QEvent *event) override;
+ bool event(QEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
- QRect overlayGeometry() const;
- void paintEvent(QPaintEvent *event) override;
+ QRect overlayGeometry() const;
+ void paintEvent(QPaintEvent *event) override;
};
diff --git a/src/ui/Painter.h b/src/ui/Painter.h
index 9f974116..f78b55e5 100644
--- a/src/ui/Painter.h
+++ b/src/ui/Painter.h
@@ -13,147 +13,141 @@
class Painter : public QPainter
{
public:
- explicit Painter(QPaintDevice *device)
- : QPainter(device)
- {}
-
- void drawTextLeft(int x, int y, const QString &text)
- {
- QFontMetrics m(fontMetrics());
- drawText(x, y + m.ascent(), text);
- }
-
- void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1)
- {
- QFontMetrics m(fontMetrics());
- if (textWidth < 0) {
- textWidth = m.horizontalAdvance(text);
- }
- drawText((outerw - x - textWidth), y + m.ascent(), text);
- }
-
- void drawPixmapLeft(int x, int y, const QPixmap &pix, const QRect &from)
- {
- drawPixmap(QPoint(x, y), pix, from);
- }
-
- void drawPixmapLeft(const QPoint &p, const QPixmap &pix, const QRect &from)
- {
- return drawPixmapLeft(p.x(), p.y(), pix, from);
- }
-
- void drawPixmapLeft(int x, int y, int w, int h, const QPixmap &pix, const QRect &from)
- {
- drawPixmap(QRect(x, y, w, h), pix, from);
- }
-
- void drawPixmapLeft(const QRect &r, const QPixmap &pix, const QRect &from)
- {
- return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), pix, from);
- }
-
- void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix)
- {
- Q_UNUSED(outerw);
- drawPixmap(QPoint(x, y), pix);
- }
-
- void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix)
- {
- return drawPixmapLeft(p.x(), p.y(), outerw, pix);
- }
-
- void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from)
- {
- drawPixmap(
- QPoint((outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from);
- }
-
- void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from)
- {
- return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
- }
- void drawPixmapRight(int x,
- int y,
- int w,
- int h,
- int outerw,
- const QPixmap &pix,
- const QRect &from)
- {
- drawPixmap(QRect((outerw - x - w), y, w, h), pix, from);
- }
-
- void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from)
- {
- return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
- }
-
- void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix)
- {
- drawPixmap(QPoint((outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix);
- }
-
- void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix)
- {
- return drawPixmapRight(p.x(), p.y(), outerw, pix);
- }
-
- void drawAvatar(const QPixmap &pix, int w, int h, int d)
- {
- QPainterPath pp;
- pp.addEllipse((w - d) / 2, (h - d) / 2, d, d);
-
- QRect region((w - d) / 2, (h - d) / 2, d, d);
-
- setClipPath(pp);
- drawPixmap(region, pix);
- }
-
- void drawLetterAvatar(const QString &c,
- const QColor &penColor,
- const QColor &brushColor,
- int w,
- int h,
- int d)
- {
- QRect region((w - d) / 2, (h - d) / 2, d, d);
-
- setPen(Qt::NoPen);
- setBrush(brushColor);
-
- drawEllipse(region.center(), d / 2, d / 2);
-
- setBrush(Qt::NoBrush);
- drawEllipse(region.center(), d / 2, d / 2);
-
- setPen(penColor);
- drawText(region.translated(0, -1), Qt::AlignCenter, c);
- }
+ explicit Painter(QPaintDevice *device)
+ : QPainter(device)
+ {}
+
+ void drawTextLeft(int x, int y, const QString &text)
+ {
+ QFontMetrics m(fontMetrics());
+ drawText(x, y + m.ascent(), text);
+ }
+
+ void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1)
+ {
+ QFontMetrics m(fontMetrics());
+ if (textWidth < 0) {
+ textWidth = m.horizontalAdvance(text);
+ }
+ drawText((outerw - x - textWidth), y + m.ascent(), text);
+ }
+
+ void drawPixmapLeft(int x, int y, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(QPoint(x, y), pix, from);
+ }
+
+ void drawPixmapLeft(const QPoint &p, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapLeft(p.x(), p.y(), pix, from);
+ }
+
+ void drawPixmapLeft(int x, int y, int w, int h, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(QRect(x, y, w, h), pix, from);
+ }
+
+ void drawPixmapLeft(const QRect &r, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), pix, from);
+ }
+
+ void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix)
+ {
+ Q_UNUSED(outerw);
+ drawPixmap(QPoint(x, y), pix);
+ }
+
+ void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix)
+ {
+ return drawPixmapLeft(p.x(), p.y(), outerw, pix);
+ }
+
+ void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(QPoint((outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from);
+ }
+
+ void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
+ }
+ void
+ drawPixmapRight(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(QRect((outerw - x - w), y, w, h), pix, from);
+ }
+
+ void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
+ }
+
+ void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix)
+ {
+ drawPixmap(QPoint((outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix);
+ }
+
+ void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix)
+ {
+ return drawPixmapRight(p.x(), p.y(), outerw, pix);
+ }
+
+ void drawAvatar(const QPixmap &pix, int w, int h, int d)
+ {
+ QPainterPath pp;
+ pp.addEllipse((w - d) / 2, (h - d) / 2, d, d);
+
+ QRect region((w - d) / 2, (h - d) / 2, d, d);
+
+ setClipPath(pp);
+ drawPixmap(region, pix);
+ }
+
+ void drawLetterAvatar(const QString &c,
+ const QColor &penColor,
+ const QColor &brushColor,
+ int w,
+ int h,
+ int d)
+ {
+ QRect region((w - d) / 2, (h - d) / 2, d, d);
+
+ setPen(Qt::NoPen);
+ setBrush(brushColor);
+
+ drawEllipse(region.center(), d / 2, d / 2);
+
+ setBrush(Qt::NoBrush);
+ drawEllipse(region.center(), d / 2, d / 2);
+
+ setPen(penColor);
+ drawText(region.translated(0, -1), Qt::AlignCenter, c);
+ }
};
class PainterHighQualityEnabler
{
public:
- PainterHighQualityEnabler(Painter &p)
- : _painter(p)
- {
- hints_ = QPainter::Antialiasing | QPainter::SmoothPixmapTransform |
- QPainter::TextAntialiasing;
+ PainterHighQualityEnabler(Painter &p)
+ : _painter(p)
+ {
+ hints_ =
+ QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing;
- _painter.setRenderHints(hints_);
- }
+ _painter.setRenderHints(hints_);
+ }
- ~PainterHighQualityEnabler()
- {
- if (hints_)
- _painter.setRenderHints(hints_, false);
- }
+ ~PainterHighQualityEnabler()
+ {
+ if (hints_)
+ _painter.setRenderHints(hints_, false);
+ }
- PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete;
- PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete;
+ PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete;
+ PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete;
private:
- Painter &_painter;
- QPainter::RenderHints hints_ = {};
+ Painter &_painter;
+ QPainter::RenderHints hints_ = {};
};
diff --git a/src/ui/RaisedButton.cpp b/src/ui/RaisedButton.cpp
index 563cb8e5..fd0cbd76 100644
--- a/src/ui/RaisedButton.cpp
+++ b/src/ui/RaisedButton.cpp
@@ -10,68 +10,68 @@
void
RaisedButton::init()
{
- shadow_state_machine_ = new QStateMachine(this);
- normal_state_ = new QState;
- pressed_state_ = new QState;
- effect_ = new QGraphicsDropShadowEffect;
+ shadow_state_machine_ = new QStateMachine(this);
+ normal_state_ = new QState;
+ pressed_state_ = new QState;
+ effect_ = new QGraphicsDropShadowEffect;
- effect_->setBlurRadius(7);
- effect_->setOffset(QPointF(0, 2));
- effect_->setColor(QColor(0, 0, 0, 75));
+ effect_->setBlurRadius(7);
+ effect_->setOffset(QPointF(0, 2));
+ effect_->setColor(QColor(0, 0, 0, 75));
- setBackgroundMode(Qt::OpaqueMode);
- setMinimumHeight(42);
- setGraphicsEffect(effect_);
- setBaseOpacity(0.3);
+ setBackgroundMode(Qt::OpaqueMode);
+ setMinimumHeight(42);
+ setGraphicsEffect(effect_);
+ setBaseOpacity(0.3);
- shadow_state_machine_->addState(normal_state_);
- shadow_state_machine_->addState(pressed_state_);
+ shadow_state_machine_->addState(normal_state_);
+ shadow_state_machine_->addState(pressed_state_);
- normal_state_->assignProperty(effect_, "offset", QPointF(0, 2));
- normal_state_->assignProperty(effect_, "blurRadius", 7);
+ normal_state_->assignProperty(effect_, "offset", QPointF(0, 2));
+ normal_state_->assignProperty(effect_, "blurRadius", 7);
- pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5));
- pressed_state_->assignProperty(effect_, "blurRadius", 29);
+ pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5));
+ pressed_state_->assignProperty(effect_, "blurRadius", 29);
- QAbstractTransition *transition;
+ QAbstractTransition *transition;
- transition = new QEventTransition(this, QEvent::MouseButtonPress);
- transition->setTargetState(pressed_state_);
- normal_state_->addTransition(transition);
+ transition = new QEventTransition(this, QEvent::MouseButtonPress);
+ transition->setTargetState(pressed_state_);
+ normal_state_->addTransition(transition);
- transition = new QEventTransition(this, QEvent::MouseButtonDblClick);
- transition->setTargetState(pressed_state_);
- normal_state_->addTransition(transition);
+ transition = new QEventTransition(this, QEvent::MouseButtonDblClick);
+ transition->setTargetState(pressed_state_);
+ normal_state_->addTransition(transition);
- transition = new QEventTransition(this, QEvent::MouseButtonRelease);
- transition->setTargetState(normal_state_);
- pressed_state_->addTransition(transition);
+ transition = new QEventTransition(this, QEvent::MouseButtonRelease);
+ transition->setTargetState(normal_state_);
+ pressed_state_->addTransition(transition);
- QPropertyAnimation *animation;
+ QPropertyAnimation *animation;
- animation = new QPropertyAnimation(effect_, "offset", this);
- animation->setDuration(100);
- shadow_state_machine_->addDefaultAnimation(animation);
+ animation = new QPropertyAnimation(effect_, "offset", this);
+ animation->setDuration(100);
+ shadow_state_machine_->addDefaultAnimation(animation);
- animation = new QPropertyAnimation(effect_, "blurRadius", this);
- animation->setDuration(100);
- shadow_state_machine_->addDefaultAnimation(animation);
+ animation = new QPropertyAnimation(effect_, "blurRadius", this);
+ animation->setDuration(100);
+ shadow_state_machine_->addDefaultAnimation(animation);
- shadow_state_machine_->setInitialState(normal_state_);
- shadow_state_machine_->start();
+ shadow_state_machine_->setInitialState(normal_state_);
+ shadow_state_machine_->start();
}
RaisedButton::RaisedButton(QWidget *parent)
: FlatButton(parent)
{
- init();
+ init();
}
RaisedButton::RaisedButton(const QString &text, QWidget *parent)
: FlatButton(parent)
{
- init();
- setText(text);
+ init();
+ setText(text);
}
RaisedButton::~RaisedButton() {}
@@ -79,15 +79,15 @@ RaisedButton::~RaisedButton() {}
bool
RaisedButton::event(QEvent *event)
{
- if (QEvent::EnabledChange == event->type()) {
- if (isEnabled()) {
- shadow_state_machine_->start();
- effect_->setEnabled(true);
- } else {
- shadow_state_machine_->stop();
- effect_->setEnabled(false);
- }
+ if (QEvent::EnabledChange == event->type()) {
+ if (isEnabled()) {
+ shadow_state_machine_->start();
+ effect_->setEnabled(true);
+ } else {
+ shadow_state_machine_->stop();
+ effect_->setEnabled(false);
}
+ }
- return FlatButton::event(event);
+ return FlatButton::event(event);
}
diff --git a/src/ui/RaisedButton.h b/src/ui/RaisedButton.h
index dcb579bb..277fa7ff 100644
--- a/src/ui/RaisedButton.h
+++ b/src/ui/RaisedButton.h
@@ -12,21 +12,21 @@
class RaisedButton : public FlatButton
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit RaisedButton(QWidget *parent = nullptr);
- explicit RaisedButton(const QString &text, QWidget *parent = nullptr);
- ~RaisedButton() override;
+ explicit RaisedButton(QWidget *parent = nullptr);
+ explicit RaisedButton(const QString &text, QWidget *parent = nullptr);
+ ~RaisedButton() override;
protected:
- bool event(QEvent *event) override;
+ bool event(QEvent *event) override;
private:
- void init();
+ void init();
- QStateMachine *shadow_state_machine_;
- QState *normal_state_;
- QState *pressed_state_;
- QGraphicsDropShadowEffect *effect_;
+ QStateMachine *shadow_state_machine_;
+ QState *normal_state_;
+ QState *pressed_state_;
+ QGraphicsDropShadowEffect *effect_;
};
diff --git a/src/ui/Ripple.cpp b/src/ui/Ripple.cpp
index f0455f0b..72cf2da2 100644
--- a/src/ui/Ripple.cpp
+++ b/src/ui/Ripple.cpp
@@ -14,7 +14,7 @@ Ripple::Ripple(const QPoint ¢er, QObject *parent)
, opacity_(0)
, center_(center)
{
- init();
+ init();
}
Ripple::Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent)
@@ -26,86 +26,86 @@ Ripple::Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent)
, opacity_(0)
, center_(center)
{
- init();
+ init();
}
void
Ripple::setRadius(qreal radius)
{
- Q_ASSERT(overlay_);
+ Q_ASSERT(overlay_);
- if (radius_ == radius)
- return;
+ if (radius_ == radius)
+ return;
- radius_ = radius;
- overlay_->update();
+ radius_ = radius;
+ overlay_->update();
}
void
Ripple::setOpacity(qreal opacity)
{
- Q_ASSERT(overlay_);
+ Q_ASSERT(overlay_);
- if (opacity_ == opacity)
- return;
+ if (opacity_ == opacity)
+ return;
- opacity_ = opacity;
- overlay_->update();
+ opacity_ = opacity;
+ overlay_->update();
}
void
Ripple::setColor(const QColor &color)
{
- if (brush_.color() == color)
- return;
+ if (brush_.color() == color)
+ return;
- brush_.setColor(color);
+ brush_.setColor(color);
- if (overlay_)
- overlay_->update();
+ if (overlay_)
+ overlay_->update();
}
void
Ripple::setBrush(const QBrush &brush)
{
- brush_ = brush;
+ brush_ = brush;
- if (overlay_)
- overlay_->update();
+ if (overlay_)
+ overlay_->update();
}
void
Ripple::destroy()
{
- Q_ASSERT(overlay_);
+ Q_ASSERT(overlay_);
- overlay_->removeRipple(this);
+ overlay_->removeRipple(this);
}
QPropertyAnimation *
Ripple::animate(const QByteArray &property, const QEasingCurve &easing, int duration)
{
- QPropertyAnimation *animation = new QPropertyAnimation;
- animation->setTargetObject(this);
- animation->setPropertyName(property);
- animation->setEasingCurve(easing);
- animation->setDuration(duration);
+ QPropertyAnimation *animation = new QPropertyAnimation;
+ animation->setTargetObject(this);
+ animation->setPropertyName(property);
+ animation->setEasingCurve(easing);
+ animation->setDuration(duration);
- addAnimation(animation);
+ addAnimation(animation);
- return animation;
+ return animation;
}
void
Ripple::init()
{
- setOpacityStartValue(0.5);
- setOpacityEndValue(0);
- setRadiusStartValue(0);
- setRadiusEndValue(300);
+ setOpacityStartValue(0.5);
+ setOpacityEndValue(0);
+ setRadiusStartValue(0);
+ setRadiusEndValue(300);
- brush_.setColor(Qt::black);
- brush_.setStyle(Qt::SolidPattern);
+ brush_.setColor(Qt::black);
+ brush_.setStyle(Qt::SolidPattern);
- connect(this, SIGNAL(finished()), this, SLOT(destroy()));
+ connect(this, SIGNAL(finished()), this, SLOT(destroy()));
}
diff --git a/src/ui/Ripple.h b/src/ui/Ripple.h
index 2ad42b9e..df09caba 100644
--- a/src/ui/Ripple.h
+++ b/src/ui/Ripple.h
@@ -14,136 +14,136 @@ class RippleOverlay;
class Ripple : public QParallelAnimationGroup
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(qreal radius WRITE setRadius READ radius)
- Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity)
+ Q_PROPERTY(qreal radius WRITE setRadius READ radius)
+ Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity)
public:
- explicit Ripple(const QPoint ¢er, QObject *parent = nullptr);
- Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent = nullptr);
+ explicit Ripple(const QPoint ¢er, QObject *parent = nullptr);
+ Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent = nullptr);
- inline void setOverlay(RippleOverlay *overlay);
+ inline void setOverlay(RippleOverlay *overlay);
- void setRadius(qreal radius);
- void setOpacity(qreal opacity);
- void setColor(const QColor &color);
- void setBrush(const QBrush &brush);
+ void setRadius(qreal radius);
+ void setOpacity(qreal opacity);
+ void setColor(const QColor &color);
+ void setBrush(const QBrush &brush);
- inline qreal radius() const;
- inline qreal opacity() const;
- inline QColor color() const;
- inline QBrush brush() const;
- inline QPoint center() const;
+ inline qreal radius() const;
+ inline qreal opacity() const;
+ inline QColor color() const;
+ inline QBrush brush() const;
+ inline QPoint center() const;
- inline QPropertyAnimation *radiusAnimation() const;
- inline QPropertyAnimation *opacityAnimation() const;
+ inline QPropertyAnimation *radiusAnimation() const;
+ inline QPropertyAnimation *opacityAnimation() const;
- inline void setOpacityStartValue(qreal value);
- inline void setOpacityEndValue(qreal value);
- inline void setRadiusStartValue(qreal value);
- inline void setRadiusEndValue(qreal value);
- inline void setDuration(int msecs);
+ inline void setOpacityStartValue(qreal value);
+ inline void setOpacityEndValue(qreal value);
+ inline void setRadiusStartValue(qreal value);
+ inline void setRadiusEndValue(qreal value);
+ inline void setDuration(int msecs);
protected slots:
- void destroy();
+ void destroy();
private:
- Q_DISABLE_COPY(Ripple)
+ Q_DISABLE_COPY(Ripple)
- QPropertyAnimation *animate(const QByteArray &property,
- const QEasingCurve &easing = QEasingCurve::OutQuad,
- int duration = 800);
+ QPropertyAnimation *animate(const QByteArray &property,
+ const QEasingCurve &easing = QEasingCurve::OutQuad,
+ int duration = 800);
- void init();
+ void init();
- RippleOverlay *overlay_;
+ RippleOverlay *overlay_;
- QPropertyAnimation *const radius_anim_;
- QPropertyAnimation *const opacity_anim_;
+ QPropertyAnimation *const radius_anim_;
+ QPropertyAnimation *const opacity_anim_;
- qreal radius_;
- qreal opacity_;
+ qreal radius_;
+ qreal opacity_;
- QPoint center_;
- QBrush brush_;
+ QPoint center_;
+ QBrush brush_;
};
inline void
Ripple::setOverlay(RippleOverlay *overlay)
{
- overlay_ = overlay;
+ overlay_ = overlay;
}
inline qreal
Ripple::radius() const
{
- return radius_;
+ return radius_;
}
inline qreal
Ripple::opacity() const
{
- return opacity_;
+ return opacity_;
}
inline QColor
Ripple::color() const
{
- return brush_.color();
+ return brush_.color();
}
inline QBrush
Ripple::brush() const
{
- return brush_;
+ return brush_;
}
inline QPoint
Ripple::center() const
{
- return center_;
+ return center_;
}
inline QPropertyAnimation *
Ripple::radiusAnimation() const
{
- return radius_anim_;
+ return radius_anim_;
}
inline QPropertyAnimation *
Ripple::opacityAnimation() const
{
- return opacity_anim_;
+ return opacity_anim_;
}
inline void
Ripple::setOpacityStartValue(qreal value)
{
- opacity_anim_->setStartValue(value);
+ opacity_anim_->setStartValue(value);
}
inline void
Ripple::setOpacityEndValue(qreal value)
{
- opacity_anim_->setEndValue(value);
+ opacity_anim_->setEndValue(value);
}
inline void
Ripple::setRadiusStartValue(qreal value)
{
- radius_anim_->setStartValue(value);
+ radius_anim_->setStartValue(value);
}
inline void
Ripple::setRadiusEndValue(qreal value)
{
- radius_anim_->setEndValue(value);
+ radius_anim_->setEndValue(value);
}
inline void
Ripple::setDuration(int msecs)
{
- radius_anim_->setDuration(msecs);
- opacity_anim_->setDuration(msecs);
+ radius_anim_->setDuration(msecs);
+ opacity_anim_->setDuration(msecs);
}
diff --git a/src/ui/RippleOverlay.cpp b/src/ui/RippleOverlay.cpp
index 00915deb..6745cc37 100644
--- a/src/ui/RippleOverlay.cpp
+++ b/src/ui/RippleOverlay.cpp
@@ -11,56 +11,56 @@ RippleOverlay::RippleOverlay(QWidget *parent)
: OverlayWidget(parent)
, use_clip_(false)
{
- setAttribute(Qt::WA_TransparentForMouseEvents);
- setAttribute(Qt::WA_NoSystemBackground);
+ setAttribute(Qt::WA_TransparentForMouseEvents);
+ setAttribute(Qt::WA_NoSystemBackground);
}
void
RippleOverlay::addRipple(Ripple *ripple)
{
- ripple->setOverlay(this);
- ripples_.push_back(ripple);
- ripple->start();
+ ripple->setOverlay(this);
+ ripples_.push_back(ripple);
+ ripple->start();
}
void
RippleOverlay::addRipple(const QPoint &position, qreal radius)
{
- Ripple *ripple = new Ripple(position);
- ripple->setRadiusEndValue(radius);
- addRipple(ripple);
+ Ripple *ripple = new Ripple(position);
+ ripple->setRadiusEndValue(radius);
+ addRipple(ripple);
}
void
RippleOverlay::removeRipple(Ripple *ripple)
{
- if (ripples_.removeOne(ripple))
- delete ripple;
+ if (ripples_.removeOne(ripple))
+ delete ripple;
}
void
RippleOverlay::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event)
+ Q_UNUSED(event)
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
- painter.setPen(Qt::NoPen);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(Qt::NoPen);
- if (use_clip_)
- painter.setClipPath(clip_path_);
+ if (use_clip_)
+ painter.setClipPath(clip_path_);
- for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it)
- paintRipple(&painter, *it);
+ for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it)
+ paintRipple(&painter, *it);
}
void
RippleOverlay::paintRipple(QPainter *painter, Ripple *ripple)
{
- const qreal radius = ripple->radius();
- const QPointF center = ripple->center();
+ const qreal radius = ripple->radius();
+ const QPointF center = ripple->center();
- painter->setOpacity(ripple->opacity());
- painter->setBrush(ripple->brush());
- painter->drawEllipse(center, radius, radius);
+ painter->setOpacity(ripple->opacity());
+ painter->setBrush(ripple->brush());
+ painter->drawEllipse(center, radius, radius);
}
diff --git a/src/ui/RippleOverlay.h b/src/ui/RippleOverlay.h
index 7ae3e4f1..3256c28d 100644
--- a/src/ui/RippleOverlay.h
+++ b/src/ui/RippleOverlay.h
@@ -12,50 +12,50 @@ class Ripple;
class RippleOverlay : public OverlayWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit RippleOverlay(QWidget *parent = nullptr);
+ explicit RippleOverlay(QWidget *parent = nullptr);
- void addRipple(Ripple *ripple);
- void addRipple(const QPoint &position, qreal radius = 300);
+ void addRipple(Ripple *ripple);
+ void addRipple(const QPoint &position, qreal radius = 300);
- void removeRipple(Ripple *ripple);
+ void removeRipple(Ripple *ripple);
- inline void setClipping(bool enable);
- inline bool hasClipping() const;
+ inline void setClipping(bool enable);
+ inline bool hasClipping() const;
- inline void setClipPath(const QPainterPath &path);
+ inline void setClipPath(const QPainterPath &path);
protected:
- void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+ void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
private:
- Q_DISABLE_COPY(RippleOverlay)
+ Q_DISABLE_COPY(RippleOverlay)
- void paintRipple(QPainter *painter, Ripple *ripple);
+ void paintRipple(QPainter *painter, Ripple *ripple);
- QList<Ripple *> ripples_;
- QPainterPath clip_path_;
- bool use_clip_;
+ QList<Ripple *> ripples_;
+ QPainterPath clip_path_;
+ bool use_clip_;
};
inline void
RippleOverlay::setClipping(bool enable)
{
- use_clip_ = enable;
- update();
+ use_clip_ = enable;
+ update();
}
inline bool
RippleOverlay::hasClipping() const
{
- return use_clip_;
+ return use_clip_;
}
inline void
RippleOverlay::setClipPath(const QPainterPath &path)
{
- clip_path_ = path;
- update();
+ clip_path_ = path;
+ update();
}
diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp
index 2fb93325..ed8a7cd8 100644
--- a/src/ui/RoomSettings.cpp
+++ b/src/ui/RoomSettings.cpp
@@ -27,479 +27,473 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
: QWidget(parent)
, roomId_{roomId}
{
- setAutoFillBackground(true);
- setAttribute(Qt::WA_DeleteOnClose, true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
-
- QFont largeFont;
- largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4);
- setMinimumWidth(conf::window::minModalWidth);
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
-
- auto layout = new QVBoxLayout(this);
-
- applyBtn_ = new QPushButton(tr("Apply"), this);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
- cancelBtn_->setDefault(true);
-
- auto btnLayout = new QHBoxLayout;
- btnLayout->addStretch(1);
- btnLayout->setSpacing(15);
- btnLayout->addWidget(cancelBtn_);
- btnLayout->addWidget(applyBtn_);
-
- nameInput_ = new TextField(this);
- nameInput_->setLabel(tr("Name").toUpper());
- topicInput_ = new TextField(this);
- topicInput_->setLabel(tr("Topic").toUpper());
-
- errorField_ = new QLabel(this);
- errorField_->setWordWrap(true);
- errorField_->hide();
-
- layout->addWidget(nameInput_);
- layout->addWidget(topicInput_);
- layout->addLayout(btnLayout, 1);
-
- auto labelLayout = new QHBoxLayout;
- labelLayout->setAlignment(Qt::AlignHCenter);
- labelLayout->addWidget(errorField_);
- layout->addLayout(labelLayout);
-
- connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked);
- connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
-
- auto window = QApplication::activeWindow();
-
- if (window != nullptr) {
- auto center = window->frameGeometry().center();
- move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
- }
+ setAutoFillBackground(true);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
+ setWindowModality(Qt::WindowModal);
+
+ QFont largeFont;
+ largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4);
+ setMinimumWidth(conf::window::minModalWidth);
+ setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
+ auto layout = new QVBoxLayout(this);
+
+ applyBtn_ = new QPushButton(tr("Apply"), this);
+ cancelBtn_ = new QPushButton(tr("Cancel"), this);
+ cancelBtn_->setDefault(true);
+
+ auto btnLayout = new QHBoxLayout;
+ btnLayout->addStretch(1);
+ btnLayout->setSpacing(15);
+ btnLayout->addWidget(cancelBtn_);
+ btnLayout->addWidget(applyBtn_);
+
+ nameInput_ = new TextField(this);
+ nameInput_->setLabel(tr("Name").toUpper());
+ topicInput_ = new TextField(this);
+ topicInput_->setLabel(tr("Topic").toUpper());
+
+ errorField_ = new QLabel(this);
+ errorField_->setWordWrap(true);
+ errorField_->hide();
+
+ layout->addWidget(nameInput_);
+ layout->addWidget(topicInput_);
+ layout->addLayout(btnLayout, 1);
+
+ auto labelLayout = new QHBoxLayout;
+ labelLayout->setAlignment(Qt::AlignHCenter);
+ labelLayout->addWidget(errorField_);
+ layout->addLayout(labelLayout);
+
+ connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked);
+ connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
+
+ auto window = QApplication::activeWindow();
+
+ if (window != nullptr) {
+ auto center = window->frameGeometry().center();
+ move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
+ }
}
void
EditModal::topicEventSent(const QString &topic)
{
- errorField_->hide();
- emit topicChanged(topic);
- close();
+ errorField_->hide();
+ emit topicChanged(topic);
+ close();
}
void
EditModal::nameEventSent(const QString &name)
{
- errorField_->hide();
- emit nameChanged(name);
- close();
+ errorField_->hide();
+ emit nameChanged(name);
+ close();
}
void
EditModal::error(const QString &msg)
{
- errorField_->setText(msg);
- errorField_->show();
+ errorField_->setText(msg);
+ errorField_->show();
}
void
EditModal::applyClicked()
{
- // Check if the values are changed from the originals.
- auto newName = nameInput_->text().trimmed();
- auto newTopic = topicInput_->text().trimmed();
+ // Check if the values are changed from the originals.
+ auto newName = nameInput_->text().trimmed();
+ auto newTopic = topicInput_->text().trimmed();
- errorField_->hide();
+ errorField_->hide();
- if (newName == initialName_ && newTopic == initialTopic_) {
- close();
- return;
- }
+ if (newName == initialName_ && newTopic == initialTopic_) {
+ close();
+ return;
+ }
- using namespace mtx::events;
- auto proxy = std::make_shared<ThreadProxy>();
- connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent);
- connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent);
- connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error);
-
- if (newName != initialName_ && !newName.isEmpty()) {
- state::Name body;
- body.name = newName.toStdString();
-
- http::client()->send_state_event(
- roomId_.toStdString(),
- body,
- [proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
- if (err) {
- emit proxy->error(
- QString::fromStdString(err->matrix_error.error));
- return;
- }
-
- emit proxy->nameEventSent(newName);
- });
- }
+ using namespace mtx::events;
+ auto proxy = std::make_shared<ThreadProxy>();
+ connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent);
+ connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent);
+ connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error);
- if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
- state::Topic body;
- body.topic = newTopic.toStdString();
-
- http::client()->send_state_event(
- roomId_.toStdString(),
- body,
- [proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
- if (err) {
- emit proxy->error(
- QString::fromStdString(err->matrix_error.error));
- return;
- }
-
- emit proxy->topicEventSent(newTopic);
- });
- }
+ if (newName != initialName_ && !newName.isEmpty()) {
+ state::Name body;
+ body.name = newName.toStdString();
+
+ http::client()->send_state_event(
+ roomId_.toStdString(),
+ body,
+ [proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit proxy->error(QString::fromStdString(err->matrix_error.error));
+ return;
+ }
+
+ emit proxy->nameEventSent(newName);
+ });
+ }
+
+ if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
+ state::Topic body;
+ body.topic = newTopic.toStdString();
+
+ http::client()->send_state_event(
+ roomId_.toStdString(),
+ body,
+ [proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit proxy->error(QString::fromStdString(err->matrix_error.error));
+ return;
+ }
+
+ emit proxy->topicEventSent(newTopic);
+ });
+ }
}
void
EditModal::setFields(const QString &roomName, const QString &roomTopic)
{
- initialName_ = roomName;
- initialTopic_ = roomTopic;
+ initialName_ = roomName;
+ initialTopic_ = roomTopic;
- nameInput_->setText(roomName);
- topicInput_->setText(roomTopic);
+ nameInput_->setText(roomName);
+ topicInput_->setText(roomTopic);
}
RoomSettings::RoomSettings(QString roomid, QObject *parent)
: QObject(parent)
, roomid_{std::move(roomid)}
{
- retrieveRoomInfo();
-
- // get room setting notifications
- http::client()->get_pushrules(
- "global",
- "override",
- roomid_.toStdString(),
- [this](const mtx::pushrules::PushRule &rule, mtx::http::RequestErr &err) {
- if (err) {
- if (err->status_code == 404)
- http::client()->get_pushrules(
- "global",
- "room",
- roomid_.toStdString(),
- [this](const mtx::pushrules::PushRule &rule,
- mtx::http::RequestErr &err) {
- if (err) {
- notifications_ = 2; // all messages
- emit notificationsChanged();
- return;
- }
-
- if (rule.enabled) {
- notifications_ = 1; // mentions only
- emit notificationsChanged();
- }
- });
- return;
- }
-
- if (rule.enabled) {
- notifications_ = 0; // muted
- emit notificationsChanged();
- } else {
- notifications_ = 2; // all messages
- emit notificationsChanged();
- }
- });
-
- // access rules
- if (info_.join_rule == state::JoinRule::Public) {
- if (info_.guest_access) {
- accessRules_ = 0;
- } else {
- accessRules_ = 1;
- }
- } else if (info_.join_rule == state::JoinRule::Invite) {
- accessRules_ = 2;
- } else if (info_.join_rule == state::JoinRule::Knock) {
- accessRules_ = 3;
- } else if (info_.join_rule == state::JoinRule::Restricted) {
- accessRules_ = 4;
+ retrieveRoomInfo();
+
+ // get room setting notifications
+ http::client()->get_pushrules(
+ "global",
+ "override",
+ roomid_.toStdString(),
+ [this](const mtx::pushrules::PushRule &rule, mtx::http::RequestErr &err) {
+ if (err) {
+ if (err->status_code == 404)
+ http::client()->get_pushrules(
+ "global",
+ "room",
+ roomid_.toStdString(),
+ [this](const mtx::pushrules::PushRule &rule, mtx::http::RequestErr &err) {
+ if (err) {
+ notifications_ = 2; // all messages
+ emit notificationsChanged();
+ return;
+ }
+
+ if (rule.enabled) {
+ notifications_ = 1; // mentions only
+ emit notificationsChanged();
+ }
+ });
+ return;
+ }
+
+ if (rule.enabled) {
+ notifications_ = 0; // muted
+ emit notificationsChanged();
+ } else {
+ notifications_ = 2; // all messages
+ emit notificationsChanged();
+ }
+ });
+
+ // access rules
+ if (info_.join_rule == state::JoinRule::Public) {
+ if (info_.guest_access) {
+ accessRules_ = 0;
+ } else {
+ accessRules_ = 1;
}
- emit accessJoinRulesChanged();
+ } else if (info_.join_rule == state::JoinRule::Invite) {
+ accessRules_ = 2;
+ } else if (info_.join_rule == state::JoinRule::Knock) {
+ accessRules_ = 3;
+ } else if (info_.join_rule == state::JoinRule::Restricted) {
+ accessRules_ = 4;
+ }
+ emit accessJoinRulesChanged();
}
QString
RoomSettings::roomName() const
{
- return utils::replaceEmoji(QString::fromStdString(info_.name).toHtmlEscaped());
+ return utils::replaceEmoji(QString::fromStdString(info_.name).toHtmlEscaped());
}
QString
RoomSettings::roomTopic() const
{
- return utils::replaceEmoji(utils::linkifyMessage(
- QString::fromStdString(info_.topic).toHtmlEscaped().replace("\n", "<br>")));
+ return utils::replaceEmoji(utils::linkifyMessage(
+ QString::fromStdString(info_.topic).toHtmlEscaped().replace("\n", "<br>")));
}
QString
RoomSettings::roomId() const
{
- return roomid_;
+ return roomid_;
}
QString
RoomSettings::roomVersion() const
{
- return QString::fromStdString(info_.version);
+ return QString::fromStdString(info_.version);
}
bool
RoomSettings::isLoading() const
{
- return isLoading_;
+ return isLoading_;
}
QString
RoomSettings::roomAvatarUrl()
{
- return QString::fromStdString(info_.avatar_url);
+ return QString::fromStdString(info_.avatar_url);
}
int
RoomSettings::memberCount() const
{
- return info_.member_count;
+ return info_.member_count;
}
void
RoomSettings::retrieveRoomInfo()
{
- try {
- usesEncryption_ = cache::isRoomEncrypted(roomid_.toStdString());
- info_ = cache::singleRoomInfo(roomid_.toStdString());
- } catch (const lmdb::error &) {
- nhlog::db()->warn("failed to retrieve room info from cache: {}",
- roomid_.toStdString());
- }
+ try {
+ usesEncryption_ = cache::isRoomEncrypted(roomid_.toStdString());
+ info_ = cache::singleRoomInfo(roomid_.toStdString());
+ } catch (const lmdb::error &) {
+ nhlog::db()->warn("failed to retrieve room info from cache: {}", roomid_.toStdString());
+ }
}
int
RoomSettings::notifications()
{
- return notifications_;
+ return notifications_;
}
int
RoomSettings::accessJoinRules()
{
- return accessRules_;
+ return accessRules_;
}
void
RoomSettings::enableEncryption()
{
- if (usesEncryption_)
- return;
-
- const auto room_id = roomid_.toStdString();
- http::client()->enable_encryption(
- room_id, [room_id, this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
- if (err) {
- int status_code = static_cast<int>(err->status_code);
- nhlog::net()->warn("failed to enable encryption in room ({}): {} {}",
- room_id,
- err->matrix_error.error,
- status_code);
- emit displayError(
- tr("Failed to enable encryption: %1")
- .arg(QString::fromStdString(err->matrix_error.error)));
- usesEncryption_ = false;
- emit encryptionChanged();
- return;
- }
-
- nhlog::net()->info("enabled encryption on room ({})", room_id);
- });
-
- usesEncryption_ = true;
- emit encryptionChanged();
+ if (usesEncryption_)
+ return;
+
+ const auto room_id = roomid_.toStdString();
+ http::client()->enable_encryption(
+ room_id, [room_id, this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ int status_code = static_cast<int>(err->status_code);
+ nhlog::net()->warn("failed to enable encryption in room ({}): {} {}",
+ room_id,
+ err->matrix_error.error,
+ status_code);
+ emit displayError(tr("Failed to enable encryption: %1")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ usesEncryption_ = false;
+ emit encryptionChanged();
+ return;
+ }
+
+ nhlog::net()->info("enabled encryption on room ({})", room_id);
+ });
+
+ usesEncryption_ = true;
+ emit encryptionChanged();
}
bool
RoomSettings::canChangeJoinRules() const
{
- try {
- return cache::hasEnoughPowerLevel({EventType::RoomJoinRules},
- roomid_.toStdString(),
- utils::localUser().toStdString());
- } catch (const lmdb::error &e) {
- nhlog::db()->warn("lmdb error: {}", e.what());
- }
-
- return false;
+ try {
+ return cache::hasEnoughPowerLevel(
+ {EventType::RoomJoinRules}, roomid_.toStdString(), utils::localUser().toStdString());
+ } catch (const lmdb::error &e) {
+ nhlog::db()->warn("lmdb error: {}", e.what());
+ }
+
+ return false;
}
bool
RoomSettings::canChangeNameAndTopic() const
{
- try {
- return cache::hasEnoughPowerLevel({EventType::RoomName, EventType::RoomTopic},
- roomid_.toStdString(),
- utils::localUser().toStdString());
- } catch (const lmdb::error &e) {
- nhlog::db()->warn("lmdb error: {}", e.what());
- }
-
- return false;
+ try {
+ return cache::hasEnoughPowerLevel({EventType::RoomName, EventType::RoomTopic},
+ roomid_.toStdString(),
+ utils::localUser().toStdString());
+ } catch (const lmdb::error &e) {
+ nhlog::db()->warn("lmdb error: {}", e.what());
+ }
+
+ return false;
}
bool
RoomSettings::canChangeAvatar() const
{
- try {
- return cache::hasEnoughPowerLevel(
- {EventType::RoomAvatar}, roomid_.toStdString(), utils::localUser().toStdString());
- } catch (const lmdb::error &e) {
- nhlog::db()->warn("lmdb error: {}", e.what());
- }
-
- return false;
+ try {
+ return cache::hasEnoughPowerLevel(
+ {EventType::RoomAvatar}, roomid_.toStdString(), utils::localUser().toStdString());
+ } catch (const lmdb::error &e) {
+ nhlog::db()->warn("lmdb error: {}", e.what());
+ }
+
+ return false;
}
bool
RoomSettings::isEncryptionEnabled() const
{
- return usesEncryption_;
+ return usesEncryption_;
}
bool
RoomSettings::supportsKnocking() const
{
- return info_.version != "" && info_.version != "1" && info_.version != "2" &&
- info_.version != "3" && info_.version != "4" && info_.version != "5" &&
- info_.version != "6";
+ return info_.version != "" && info_.version != "1" && info_.version != "2" &&
+ info_.version != "3" && info_.version != "4" && info_.version != "5" &&
+ info_.version != "6";
}
bool
RoomSettings::supportsRestricted() const
{
- return info_.version != "" && info_.version != "1" && info_.version != "2" &&
- info_.version != "3" && info_.version != "4" && info_.version != "5" &&
- info_.version != "6" && info_.version != "7";
+ return info_.version != "" && info_.version != "1" && info_.version != "2" &&
+ info_.version != "3" && info_.version != "4" && info_.version != "5" &&
+ info_.version != "6" && info_.version != "7";
}
void
RoomSettings::openEditModal()
{
- retrieveRoomInfo();
-
- auto modal = new EditModal(roomid_);
- modal->setFields(QString::fromStdString(info_.name), QString::fromStdString(info_.topic));
- modal->raise();
- modal->show();
- connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) {
- info_.name = newName.toStdString();
- emit roomNameChanged();
- });
-
- connect(modal, &EditModal::topicChanged, this, [this](const QString &newTopic) {
- info_.topic = newTopic.toStdString();
- emit roomTopicChanged();
- });
+ retrieveRoomInfo();
+
+ auto modal = new EditModal(roomid_);
+ modal->setFields(QString::fromStdString(info_.name), QString::fromStdString(info_.topic));
+ modal->raise();
+ modal->show();
+ connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) {
+ info_.name = newName.toStdString();
+ emit roomNameChanged();
+ });
+
+ connect(modal, &EditModal::topicChanged, this, [this](const QString &newTopic) {
+ info_.topic = newTopic.toStdString();
+ emit roomTopicChanged();
+ });
}
void
RoomSettings::changeNotifications(int currentIndex)
{
- notifications_ = currentIndex;
-
- std::string room_id = roomid_.toStdString();
- if (notifications_ == 0) {
- // mute room
- // delete old rule first, then add new rule
- mtx::pushrules::PushRule rule;
- rule.actions = {mtx::pushrules::actions::dont_notify{}};
- mtx::pushrules::PushCondition condition;
- condition.kind = "event_match";
- condition.key = "room_id";
- condition.pattern = room_id;
- rule.conditions = {condition};
-
- http::client()->put_pushrules(
- "global", "override", room_id, rule, [room_id](mtx::http::RequestErr &err) {
- if (err)
- nhlog::net()->error("failed to set pushrule for room {}: {} {}",
- room_id,
- static_cast<int>(err->status_code),
- err->matrix_error.error);
- http::client()->delete_pushrules(
- "global", "room", room_id, [room_id](mtx::http::RequestErr &) {});
- });
- } else if (notifications_ == 1) {
- // mentions only
- // delete old rule first, then add new rule
- mtx::pushrules::PushRule rule;
- rule.actions = {mtx::pushrules::actions::dont_notify{}};
- http::client()->put_pushrules(
- "global", "room", room_id, rule, [room_id](mtx::http::RequestErr &err) {
- if (err)
- nhlog::net()->error("failed to set pushrule for room {}: {} {}",
- room_id,
- static_cast<int>(err->status_code),
- err->matrix_error.error);
- http::client()->delete_pushrules(
- "global", "override", room_id, [room_id](mtx::http::RequestErr &) {});
- });
- } else {
- // all messages
- http::client()->delete_pushrules(
- "global", "override", room_id, [room_id](mtx::http::RequestErr &) {
- http::client()->delete_pushrules(
- "global", "room", room_id, [room_id](mtx::http::RequestErr &) {});
- });
- }
+ notifications_ = currentIndex;
+
+ std::string room_id = roomid_.toStdString();
+ if (notifications_ == 0) {
+ // mute room
+ // delete old rule first, then add new rule
+ mtx::pushrules::PushRule rule;
+ rule.actions = {mtx::pushrules::actions::dont_notify{}};
+ mtx::pushrules::PushCondition condition;
+ condition.kind = "event_match";
+ condition.key = "room_id";
+ condition.pattern = room_id;
+ rule.conditions = {condition};
+
+ http::client()->put_pushrules(
+ "global", "override", room_id, rule, [room_id](mtx::http::RequestErr &err) {
+ if (err)
+ nhlog::net()->error("failed to set pushrule for room {}: {} {}",
+ room_id,
+ static_cast<int>(err->status_code),
+ err->matrix_error.error);
+ http::client()->delete_pushrules(
+ "global", "room", room_id, [room_id](mtx::http::RequestErr &) {});
+ });
+ } else if (notifications_ == 1) {
+ // mentions only
+ // delete old rule first, then add new rule
+ mtx::pushrules::PushRule rule;
+ rule.actions = {mtx::pushrules::actions::dont_notify{}};
+ http::client()->put_pushrules(
+ "global", "room", room_id, rule, [room_id](mtx::http::RequestErr &err) {
+ if (err)
+ nhlog::net()->error("failed to set pushrule for room {}: {} {}",
+ room_id,
+ static_cast<int>(err->status_code),
+ err->matrix_error.error);
+ http::client()->delete_pushrules(
+ "global", "override", room_id, [room_id](mtx::http::RequestErr &) {});
+ });
+ } else {
+ // all messages
+ http::client()->delete_pushrules(
+ "global", "override", room_id, [room_id](mtx::http::RequestErr &) {
+ http::client()->delete_pushrules(
+ "global", "room", room_id, [room_id](mtx::http::RequestErr &) {});
+ });
+ }
}
void
RoomSettings::changeAccessRules(int index)
{
- using namespace mtx::events::state;
-
- auto guest_access = [](int index) -> state::GuestAccess {
- state::GuestAccess event;
-
- if (index == 0)
- event.guest_access = state::AccessState::CanJoin;
- else
- event.guest_access = state::AccessState::Forbidden;
-
- return event;
- }(index);
-
- auto join_rule = [](int index) -> state::JoinRules {
- state::JoinRules event;
-
- switch (index) {
- case 0:
- case 1:
- event.join_rule = state::JoinRule::Public;
- break;
- case 2:
- event.join_rule = state::JoinRule::Invite;
- break;
- case 3:
- event.join_rule = state::JoinRule::Knock;
- break;
- case 4:
- event.join_rule = state::JoinRule::Restricted;
- break;
- default:
- event.join_rule = state::JoinRule::Invite;
- }
+ using namespace mtx::events::state;
+
+ auto guest_access = [](int index) -> state::GuestAccess {
+ state::GuestAccess event;
+
+ if (index == 0)
+ event.guest_access = state::AccessState::CanJoin;
+ else
+ event.guest_access = state::AccessState::Forbidden;
+
+ return event;
+ }(index);
+
+ auto join_rule = [](int index) -> state::JoinRules {
+ state::JoinRules event;
+
+ switch (index) {
+ case 0:
+ case 1:
+ event.join_rule = state::JoinRule::Public;
+ break;
+ case 2:
+ event.join_rule = state::JoinRule::Invite;
+ break;
+ case 3:
+ event.join_rule = state::JoinRule::Knock;
+ break;
+ case 4:
+ event.join_rule = state::JoinRule::Restricted;
+ break;
+ default:
+ event.join_rule = state::JoinRule::Invite;
+ }
- return event;
- }(index);
+ return event;
+ }(index);
- updateAccessRules(roomid_.toStdString(), join_rule, guest_access);
+ updateAccessRules(roomid_.toStdString(), join_rule, guest_access);
}
void
@@ -507,139 +501,134 @@ RoomSettings::updateAccessRules(const std::string &room_id,
const mtx::events::state::JoinRules &join_rule,
const mtx::events::state::GuestAccess &guest_access)
{
- isLoading_ = true;
- emit loadingChanged();
+ isLoading_ = true;
+ emit loadingChanged();
+
+ http::client()->send_state_event(
+ room_id,
+ join_rule,
+ [this, room_id, guest_access](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to send m.room.join_rule: {} {}",
+ static_cast<int>(err->status_code),
+ err->matrix_error.error);
+ emit displayError(QString::fromStdString(err->matrix_error.error));
+ isLoading_ = false;
+ emit loadingChanged();
+ return;
+ }
+
+ http::client()->send_state_event(
+ room_id,
+ guest_access,
+ [this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to send m.room.guest_access: {} {}",
+ static_cast<int>(err->status_code),
+ err->matrix_error.error);
+ emit displayError(QString::fromStdString(err->matrix_error.error));
+ }
- http::client()->send_state_event(
- room_id,
- join_rule,
- [this, room_id, guest_access](const mtx::responses::EventId &,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to send m.room.join_rule: {} {}",
- static_cast<int>(err->status_code),
- err->matrix_error.error);
- emit displayError(QString::fromStdString(err->matrix_error.error));
- isLoading_ = false;
- emit loadingChanged();
- return;
- }
-
- http::client()->send_state_event(
- room_id,
- guest_access,
- [this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to send m.room.guest_access: {} {}",
- static_cast<int>(err->status_code),
- err->matrix_error.error);
- emit displayError(
- QString::fromStdString(err->matrix_error.error));
- }
-
- isLoading_ = false;
- emit loadingChanged();
- });
- });
+ isLoading_ = false;
+ emit loadingChanged();
+ });
+ });
}
void
RoomSettings::stopLoading()
{
- isLoading_ = false;
- emit loadingChanged();
+ isLoading_ = false;
+ emit loadingChanged();
}
void
RoomSettings::avatarChanged()
{
- retrieveRoomInfo();
- emit avatarUrlChanged();
+ retrieveRoomInfo();
+ emit avatarUrlChanged();
}
void
RoomSettings::updateAvatar()
{
- const QString picturesFolder =
- QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
- const QString fileName = QFileDialog::getOpenFileName(
- nullptr, tr("Select an avatar"), picturesFolder, tr("All Files (*)"));
-
- if (fileName.isEmpty())
- return;
-
- QMimeDatabase db;
- QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
-
- const auto format = mime.name().split("/")[0];
-
- QFile file{fileName, this};
- if (format != "image") {
- emit displayError(tr("The selected file is not an image"));
- return;
- }
-
- if (!file.open(QIODevice::ReadOnly)) {
- emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
- return;
- }
-
- isLoading_ = true;
- emit loadingChanged();
-
- // Events emitted from the http callbacks (different threads) will
- // be queued back into the UI thread through this proxy object.
- auto proxy = std::make_shared<ThreadProxy>();
- connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
- connect(proxy.get(), &ThreadProxy::stopLoading, this, &RoomSettings::stopLoading);
-
- const auto bin = file.peek(file.size());
- const auto payload = std::string(bin.data(), bin.size());
- const auto dimensions = QImageReader(&file).size();
-
- // First we need to create a new mxc URI
- // (i.e upload media to the Matrix content repository) for the new avatar.
- http::client()->upload(
- payload,
- mime.name().toStdString(),
- QFileInfo(fileName).fileName().toStdString(),
- [proxy = std::move(proxy),
- dimensions,
- payload,
- mimetype = mime.name().toStdString(),
- size = payload.size(),
- room_id = roomid_.toStdString(),
- content = std::move(bin)](const mtx::responses::ContentURI &res,
- mtx::http::RequestErr err) {
- if (err) {
- emit proxy->stopLoading();
- emit proxy->error(
- tr("Failed to upload image: %s")
- .arg(QString::fromStdString(err->matrix_error.error)));
- return;
- }
-
- using namespace mtx::events;
- state::Avatar avatar_event;
- avatar_event.image_info.w = dimensions.width();
- avatar_event.image_info.h = dimensions.height();
- avatar_event.image_info.mimetype = mimetype;
- avatar_event.image_info.size = size;
- avatar_event.url = res.content_uri;
-
- http::client()->send_state_event(
- room_id,
- avatar_event,
- [content = std::move(content), proxy = std::move(proxy)](
- const mtx::responses::EventId &, mtx::http::RequestErr err) {
- if (err) {
- emit proxy->error(
- tr("Failed to upload image: %s")
+ const QString picturesFolder =
+ QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+ const QString fileName = QFileDialog::getOpenFileName(
+ nullptr, tr("Select an avatar"), picturesFolder, tr("All Files (*)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
+
+ const auto format = mime.name().split("/")[0];
+
+ QFile file{fileName, this};
+ if (format != "image") {
+ emit displayError(tr("The selected file is not an image"));
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
+ return;
+ }
+
+ isLoading_ = true;
+ emit loadingChanged();
+
+ // Events emitted from the http callbacks (different threads) will
+ // be queued back into the UI thread through this proxy object.
+ auto proxy = std::make_shared<ThreadProxy>();
+ connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
+ connect(proxy.get(), &ThreadProxy::stopLoading, this, &RoomSettings::stopLoading);
+
+ const auto bin = file.peek(file.size());
+ const auto payload = std::string(bin.data(), bin.size());
+ const auto dimensions = QImageReader(&file).size();
+
+ // First we need to create a new mxc URI
+ // (i.e upload media to the Matrix content repository) for the new avatar.
+ http::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fileName).fileName().toStdString(),
+ [proxy = std::move(proxy),
+ dimensions,
+ payload,
+ mimetype = mime.name().toStdString(),
+ size = payload.size(),
+ room_id = roomid_.toStdString(),
+ content = std::move(bin)](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
+ if (err) {
+ emit proxy->stopLoading();
+ emit proxy->error(tr("Failed to upload image: %s")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ return;
+ }
+
+ using namespace mtx::events;
+ state::Avatar avatar_event;
+ avatar_event.image_info.w = dimensions.width();
+ avatar_event.image_info.h = dimensions.height();
+ avatar_event.image_info.mimetype = mimetype;
+ avatar_event.image_info.size = size;
+ avatar_event.url = res.content_uri;
+
+ http::client()->send_state_event(
+ room_id,
+ avatar_event,
+ [content = std::move(content),
+ proxy = std::move(proxy)](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit proxy->error(tr("Failed to upload image: %s")
.arg(QString::fromStdString(err->matrix_error.error)));
- return;
- }
+ return;
+ }
- emit proxy->stopLoading();
- });
- });
+ emit proxy->stopLoading();
+ });
+ });
}
diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h
index ab768ffe..6ec645e0 100644
--- a/src/ui/RoomSettings.h
+++ b/src/ui/RoomSettings.h
@@ -19,121 +19,121 @@ class TextField;
/// outside of main with the UI code.
class ThreadProxy : public QObject
{
- Q_OBJECT
+ Q_OBJECT
signals:
- void error(const QString &msg);
- void nameEventSent(const QString &);
- void topicEventSent(const QString &);
- void stopLoading();
+ void error(const QString &msg);
+ void nameEventSent(const QString &);
+ void topicEventSent(const QString &);
+ void stopLoading();
};
class EditModal : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- EditModal(const QString &roomId, QWidget *parent = nullptr);
+ EditModal(const QString &roomId, QWidget *parent = nullptr);
- void setFields(const QString &roomName, const QString &roomTopic);
+ void setFields(const QString &roomName, const QString &roomTopic);
signals:
- void nameChanged(const QString &roomName);
- void topicChanged(const QString &topic);
+ void nameChanged(const QString &roomName);
+ void topicChanged(const QString &topic);
private slots:
- void topicEventSent(const QString &topic);
- void nameEventSent(const QString &name);
- void error(const QString &msg);
+ void topicEventSent(const QString &topic);
+ void nameEventSent(const QString &name);
+ void error(const QString &msg);
- void applyClicked();
+ void applyClicked();
private:
- QString roomId_;
- QString initialName_;
- QString initialTopic_;
+ QString roomId_;
+ QString initialName_;
+ QString initialTopic_;
- QLabel *errorField_;
+ QLabel *errorField_;
- TextField *nameInput_;
- TextField *topicInput_;
+ TextField *nameInput_;
+ TextField *topicInput_;
- QPushButton *applyBtn_;
- QPushButton *cancelBtn_;
+ QPushButton *applyBtn_;
+ QPushButton *cancelBtn_;
};
class RoomSettings : public QObject
{
- Q_OBJECT
- Q_PROPERTY(QString roomId READ roomId CONSTANT)
- Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT)
- Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
- Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
- Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged)
- Q_PROPERTY(int memberCount READ memberCount CONSTANT)
- Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged)
- Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged)
- Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
- Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT)
- Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT)
- Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT)
- Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged)
- Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT)
- Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT)
+ Q_OBJECT
+ Q_PROPERTY(QString roomId READ roomId CONSTANT)
+ Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT)
+ Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
+ Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
+ Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged)
+ Q_PROPERTY(int memberCount READ memberCount CONSTANT)
+ Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged)
+ Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged)
+ Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
+ Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT)
+ Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT)
+ Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT)
+ Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged)
+ Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT)
+ Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT)
public:
- RoomSettings(QString roomid, QObject *parent = nullptr);
-
- QString roomId() const;
- QString roomName() const;
- QString roomTopic() const;
- QString roomVersion() const;
- QString roomAvatarUrl();
- int memberCount() const;
- int notifications();
- int accessJoinRules();
- bool isLoading() const;
- //! Whether the user has enough power level to send m.room.join_rules events.
- bool canChangeJoinRules() const;
- //! Whether the user has enough power level to send m.room.name & m.room.topic events.
- bool canChangeNameAndTopic() const;
- //! Whether the user has enough power level to send m.room.avatar event.
- bool canChangeAvatar() const;
- bool isEncryptionEnabled() const;
- bool supportsKnocking() const;
- bool supportsRestricted() const;
-
- Q_INVOKABLE void enableEncryption();
- Q_INVOKABLE void updateAvatar();
- Q_INVOKABLE void openEditModal();
- Q_INVOKABLE void changeAccessRules(int index);
- Q_INVOKABLE void changeNotifications(int currentIndex);
+ RoomSettings(QString roomid, QObject *parent = nullptr);
+
+ QString roomId() const;
+ QString roomName() const;
+ QString roomTopic() const;
+ QString roomVersion() const;
+ QString roomAvatarUrl();
+ int memberCount() const;
+ int notifications();
+ int accessJoinRules();
+ bool isLoading() const;
+ //! Whether the user has enough power level to send m.room.join_rules events.
+ bool canChangeJoinRules() const;
+ //! Whether the user has enough power level to send m.room.name & m.room.topic events.
+ bool canChangeNameAndTopic() const;
+ //! Whether the user has enough power level to send m.room.avatar event.
+ bool canChangeAvatar() const;
+ bool isEncryptionEnabled() const;
+ bool supportsKnocking() const;
+ bool supportsRestricted() const;
+
+ Q_INVOKABLE void enableEncryption();
+ Q_INVOKABLE void updateAvatar();
+ Q_INVOKABLE void openEditModal();
+ Q_INVOKABLE void changeAccessRules(int index);
+ Q_INVOKABLE void changeNotifications(int currentIndex);
signals:
- void loadingChanged();
- void roomNameChanged();
- void roomTopicChanged();
- void avatarUrlChanged();
- void encryptionChanged();
- void notificationsChanged();
- void accessJoinRulesChanged();
- void displayError(const QString &errorMessage);
+ void loadingChanged();
+ void roomNameChanged();
+ void roomTopicChanged();
+ void avatarUrlChanged();
+ void encryptionChanged();
+ void notificationsChanged();
+ void accessJoinRulesChanged();
+ void displayError(const QString &errorMessage);
public slots:
- void stopLoading();
- void avatarChanged();
+ void stopLoading();
+ void avatarChanged();
private:
- void retrieveRoomInfo();
- void updateAccessRules(const std::string &room_id,
- const mtx::events::state::JoinRules &,
- const mtx::events::state::GuestAccess &);
+ void retrieveRoomInfo();
+ void updateAccessRules(const std::string &room_id,
+ const mtx::events::state::JoinRules &,
+ const mtx::events::state::GuestAccess &);
private:
- QString roomid_;
- bool usesEncryption_ = false;
- bool isLoading_ = false;
- RoomInfo info_;
- int notifications_ = 0;
- int accessRules_ = 0;
+ QString roomid_;
+ bool usesEncryption_ = false;
+ bool isLoading_ = false;
+ RoomInfo info_;
+ int notifications_ = 0;
+ int accessRules_ = 0;
};
diff --git a/src/ui/SnackBar.cpp b/src/ui/SnackBar.cpp
index 18990c47..90187154 100644
--- a/src/ui/SnackBar.cpp
+++ b/src/ui/SnackBar.cpp
@@ -15,121 +15,121 @@ SnackBar::SnackBar(QWidget *parent)
: OverlayWidget(parent)
, offset_anim(this, "offset", this)
{
- QFont font;
- font.setPointSizeF(font.pointSizeF() * 1.2);
- font.setWeight(50);
- setFont(font);
+ QFont font;
+ font.setPointSizeF(font.pointSizeF() * 1.2);
+ font.setWeight(50);
+ setFont(font);
- boxHeight_ = QFontMetrics(font).height() * 2;
- offset_ = STARTING_OFFSET;
- position_ = SnackBarPosition::Top;
+ boxHeight_ = QFontMetrics(font).height() * 2;
+ offset_ = STARTING_OFFSET;
+ position_ = SnackBarPosition::Top;
- hideTimer_.setSingleShot(true);
+ hideTimer_.setSingleShot(true);
- offset_anim.setStartValue(1.0);
- offset_anim.setEndValue(0.0);
- offset_anim.setDuration(100);
- offset_anim.setEasingCurve(QEasingCurve::OutCubic);
+ offset_anim.setStartValue(1.0);
+ offset_anim.setEndValue(0.0);
+ offset_anim.setDuration(100);
+ offset_anim.setEasingCurve(QEasingCurve::OutCubic);
- connect(this, &SnackBar::offsetChanged, this, [this]() mutable { repaint(); });
- connect(
- &offset_anim, &QPropertyAnimation::finished, this, [this]() { hideTimer_.start(10000); });
+ connect(this, &SnackBar::offsetChanged, this, [this]() mutable { repaint(); });
+ connect(
+ &offset_anim, &QPropertyAnimation::finished, this, [this]() { hideTimer_.start(10000); });
- connect(&hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage()));
+ connect(&hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage()));
- hide();
+ hide();
}
void
SnackBar::start()
{
- if (messages_.empty())
- return;
+ if (messages_.empty())
+ return;
- show();
- raise();
+ show();
+ raise();
- offset_anim.start();
+ offset_anim.start();
}
void
SnackBar::hideMessage()
{
- stopTimers();
- hide();
+ stopTimers();
+ hide();
- if (!messages_.empty())
- // Moving on to the next message.
- messages_.pop_front();
+ if (!messages_.empty())
+ // Moving on to the next message.
+ messages_.pop_front();
- // Resetting the starting position of the widget.
- offset_ = STARTING_OFFSET;
+ // Resetting the starting position of the widget.
+ offset_ = STARTING_OFFSET;
- if (!messages_.empty())
- start();
+ if (!messages_.empty())
+ start();
}
void
SnackBar::stopTimers()
{
- hideTimer_.stop();
+ hideTimer_.stop();
}
void
SnackBar::showMessage(const QString &msg)
{
- messages_.push_back(msg);
+ messages_.push_back(msg);
- // There is already an active message.
- if (isVisible())
- return;
+ // There is already an active message.
+ if (isVisible())
+ return;
- start();
+ start();
}
void
SnackBar::mousePressEvent(QMouseEvent *)
{
- hideMessage();
+ hideMessage();
}
void
SnackBar::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event)
+ Q_UNUSED(event)
- if (messages_.empty())
- return;
+ if (messages_.empty())
+ return;
- auto message_ = messages_.front();
+ auto message_ = messages_.front();
- QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing);
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing);
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
- brush.setColor(bgColor_);
- p.setBrush(brush);
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
+ brush.setColor(bgColor_);
+ p.setBrush(brush);
- QRect r(0, 0, std::max(MIN_WIDTH, width() * MIN_WIDTH_PERCENTAGE), boxHeight_);
+ QRect r(0, 0, std::max(MIN_WIDTH, width() * MIN_WIDTH_PERCENTAGE), boxHeight_);
- p.setPen(Qt::white);
- QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
+ p.setPen(Qt::white);
+ QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
- p.setPen(Qt::NoPen);
- r = br.united(r).adjusted(-BOX_PADDING, -BOX_PADDING, BOX_PADDING, BOX_PADDING);
+ p.setPen(Qt::NoPen);
+ r = br.united(r).adjusted(-BOX_PADDING, -BOX_PADDING, BOX_PADDING, BOX_PADDING);
- const qreal s = 1 - offset_;
+ const qreal s = 1 - offset_;
- if (position_ == SnackBarPosition::Bottom)
- p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2,
- height() - BOX_PADDING - s * (r.height()));
- else
- p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2,
- s * (r.height()) - 2 * BOX_PADDING);
+ if (position_ == SnackBarPosition::Bottom)
+ p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2,
+ height() - BOX_PADDING - s * (r.height()));
+ else
+ p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2,
+ s * (r.height()) - 2 * BOX_PADDING);
- br.moveCenter(r.center());
- p.drawRoundedRect(r.adjusted(0, 0, 0, 4), 4, 4);
- p.setPen(textColor_);
- p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
+ br.moveCenter(r.center());
+ p.drawRoundedRect(r.adjusted(0, 0, 0, 4), 4, 4);
+ p.setPen(textColor_);
+ p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
}
diff --git a/src/ui/SnackBar.h b/src/ui/SnackBar.h
index 8d127933..0459950f 100644
--- a/src/ui/SnackBar.h
+++ b/src/ui/SnackBar.h
@@ -14,79 +14,79 @@
enum class SnackBarPosition
{
- Bottom,
- Top,
+ Bottom,
+ Top,
};
class SnackBar : public OverlayWidget
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor bgColor READ backgroundColor WRITE setBackgroundColor)
- Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor)
- Q_PROPERTY(double offset READ offset WRITE setOffset NOTIFY offsetChanged)
+ Q_PROPERTY(QColor bgColor READ backgroundColor WRITE setBackgroundColor)
+ Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor)
+ Q_PROPERTY(double offset READ offset WRITE setOffset NOTIFY offsetChanged)
public:
- explicit SnackBar(QWidget *parent);
-
- QColor backgroundColor() const { return bgColor_; }
- void setBackgroundColor(const QColor &color)
- {
- bgColor_ = color;
- update();
- }
-
- QColor textColor() const { return textColor_; }
- void setTextColor(const QColor &color)
- {
- textColor_ = color;
- update();
- }
- void setPosition(SnackBarPosition pos)
- {
- position_ = pos;
- update();
- }
-
- double offset() { return offset_; }
- void setOffset(double offset)
- {
- if (offset != offset_) {
- offset_ = offset;
- emit offsetChanged();
- }
+ explicit SnackBar(QWidget *parent);
+
+ QColor backgroundColor() const { return bgColor_; }
+ void setBackgroundColor(const QColor &color)
+ {
+ bgColor_ = color;
+ update();
+ }
+
+ QColor textColor() const { return textColor_; }
+ void setTextColor(const QColor &color)
+ {
+ textColor_ = color;
+ update();
+ }
+ void setPosition(SnackBarPosition pos)
+ {
+ position_ = pos;
+ update();
+ }
+
+ double offset() { return offset_; }
+ void setOffset(double offset)
+ {
+ if (offset != offset_) {
+ offset_ = offset;
+ emit offsetChanged();
}
+ }
public slots:
- void showMessage(const QString &msg);
+ void showMessage(const QString &msg);
signals:
- void offsetChanged();
+ void offsetChanged();
protected:
- void paintEvent(QPaintEvent *event) override;
- void mousePressEvent(QMouseEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
private slots:
- void hideMessage();
+ void hideMessage();
private:
- void stopTimers();
- void start();
+ void stopTimers();
+ void start();
- QColor bgColor_;
- QColor textColor_;
+ QColor bgColor_;
+ QColor textColor_;
- qreal bgOpacity_;
- qreal offset_;
+ qreal bgOpacity_;
+ qreal offset_;
- std::deque<QString> messages_;
+ std::deque<QString> messages_;
- QTimer hideTimer_;
+ QTimer hideTimer_;
- double boxHeight_;
+ double boxHeight_;
- QPropertyAnimation offset_anim;
+ QPropertyAnimation offset_anim;
- SnackBarPosition position_;
+ SnackBarPosition position_;
};
diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp
index 7d015e89..8f1a6aa5 100644
--- a/src/ui/TextField.cpp
+++ b/src/ui/TextField.cpp
@@ -15,363 +15,363 @@
TextField::TextField(QWidget *parent)
: QLineEdit(parent)
{
- // Get rid of the focus border on macOS.
- setAttribute(Qt::WA_MacShowFocusRect, 0);
+ // Get rid of the focus border on macOS.
+ setAttribute(Qt::WA_MacShowFocusRect, 0);
- QPalette pal;
+ QPalette pal;
- state_machine_ = new TextFieldStateMachine(this);
- label_ = nullptr;
- label_font_size_ = 15;
- show_label_ = false;
- background_color_ = pal.color(QPalette::Window);
- is_valid_ = true;
+ state_machine_ = new TextFieldStateMachine(this);
+ label_ = nullptr;
+ label_font_size_ = 15;
+ show_label_ = false;
+ background_color_ = pal.color(QPalette::Window);
+ is_valid_ = true;
- setFrame(false);
- setAttribute(Qt::WA_Hover);
- setMouseTracking(true);
- setTextMargins(0, 4, 0, 6);
+ setFrame(false);
+ setAttribute(Qt::WA_Hover);
+ setMouseTracking(true);
+ setTextMargins(0, 4, 0, 6);
- state_machine_->start();
- QCoreApplication::processEvents();
+ state_machine_->start();
+ QCoreApplication::processEvents();
}
void
TextField::setBackgroundColor(const QColor &color)
{
- background_color_ = color;
+ background_color_ = color;
}
QColor
TextField::backgroundColor() const
{
- return background_color_;
+ return background_color_;
}
void
TextField::setShowLabel(bool value)
{
- if (show_label_ == value) {
- return;
- }
-
- show_label_ = value;
-
- if (!label_ && value) {
- label_ = new TextFieldLabel(this);
- state_machine_->setLabel(label_);
- }
-
- if (value) {
- setContentsMargins(0, 23, 0, 0);
- } else {
- setContentsMargins(0, 0, 0, 0);
- }
+ if (show_label_ == value) {
+ return;
+ }
+
+ show_label_ = value;
+
+ if (!label_ && value) {
+ label_ = new TextFieldLabel(this);
+ state_machine_->setLabel(label_);
+ }
+
+ if (value) {
+ setContentsMargins(0, 23, 0, 0);
+ } else {
+ setContentsMargins(0, 0, 0, 0);
+ }
}
bool
TextField::hasLabel() const
{
- return show_label_;
+ return show_label_;
}
void
TextField::setValid(bool valid)
{
- is_valid_ = valid;
+ is_valid_ = valid;
}
bool
TextField::isValid() const
{
- QString s = text();
- int pos = 0;
- if (regexp_.pattern().isEmpty()) {
- return is_valid_;
- }
- QRegularExpressionValidator v(QRegularExpression(regexp_), 0);
- return v.validate(s, pos) == QValidator::Acceptable;
+ QString s = text();
+ int pos = 0;
+ if (regexp_.pattern().isEmpty()) {
+ return is_valid_;
+ }
+ QRegularExpressionValidator v(QRegularExpression(regexp_), 0);
+ return v.validate(s, pos) == QValidator::Acceptable;
}
void
TextField::setLabelFontSize(qreal size)
{
- label_font_size_ = size;
+ label_font_size_ = size;
- if (label_) {
- QFont font(label_->font());
- font.setPointSizeF(size);
- label_->setFont(font);
- label_->update();
- }
+ if (label_) {
+ QFont font(label_->font());
+ font.setPointSizeF(size);
+ label_->setFont(font);
+ label_->update();
+ }
}
qreal
TextField::labelFontSize() const
{
- return label_font_size_;
+ return label_font_size_;
}
void
TextField::setLabel(const QString &label)
{
- label_text_ = label;
- setShowLabel(true);
- label_->update();
+ label_text_ = label;
+ setShowLabel(true);
+ label_->update();
}
QString
TextField::label() const
{
- return label_text_;
+ return label_text_;
}
void
TextField::setLabelColor(const QColor &color)
{
- label_color_ = color;
- update();
+ label_color_ = color;
+ update();
}
QColor
TextField::labelColor() const
{
- if (!label_color_.isValid()) {
- return QPalette().color(QPalette::Text);
- }
+ if (!label_color_.isValid()) {
+ return QPalette().color(QPalette::Text);
+ }
- return label_color_;
+ return label_color_;
}
void
TextField::setInkColor(const QColor &color)
{
- ink_color_ = color;
- update();
+ ink_color_ = color;
+ update();
}
QColor
TextField::inkColor() const
{
- if (!ink_color_.isValid()) {
- return QPalette().color(QPalette::Text);
- }
+ if (!ink_color_.isValid()) {
+ return QPalette().color(QPalette::Text);
+ }
- return ink_color_;
+ return ink_color_;
}
void
TextField::setUnderlineColor(const QColor &color)
{
- underline_color_ = color;
- update();
+ underline_color_ = color;
+ update();
}
void
TextField::setRegexp(const QRegularExpression ®exp)
{
- regexp_ = regexp;
+ regexp_ = regexp;
}
QColor
TextField::underlineColor() const
{
- if (!underline_color_.isValid()) {
- if ((hasAcceptableInput() && isValid()) || !isModified())
- return QPalette().color(QPalette::Highlight);
- else
- return Qt::red;
- }
-
- return underline_color_;
+ if (!underline_color_.isValid()) {
+ if ((hasAcceptableInput() && isValid()) || !isModified())
+ return QPalette().color(QPalette::Highlight);
+ else
+ return Qt::red;
+ }
+
+ return underline_color_;
}
bool
TextField::event(QEvent *event)
{
- switch (event->type()) {
- case QEvent::Resize:
- case QEvent::Move: {
- if (label_)
- label_->setGeometry(rect());
- break;
- }
- default:
- break;
- }
-
- return QLineEdit::event(event);
+ switch (event->type()) {
+ case QEvent::Resize:
+ case QEvent::Move: {
+ if (label_)
+ label_->setGeometry(rect());
+ break;
+ }
+ default:
+ break;
+ }
+
+ return QLineEdit::event(event);
}
void
TextField::paintEvent(QPaintEvent *event)
{
- QLineEdit::paintEvent(event);
+ QLineEdit::paintEvent(event);
- QPainter painter(this);
+ QPainter painter(this);
- if (text().isEmpty()) {
- painter.setOpacity(1 - state_machine_->progress());
- painter.fillRect(rect(), backgroundColor());
- }
+ if (text().isEmpty()) {
+ painter.setOpacity(1 - state_machine_->progress());
+ painter.fillRect(rect(), backgroundColor());
+ }
- const int y = height() - 1;
- const int wd = width() - 5;
+ const int y = height() - 1;
+ const int wd = width() - 5;
- QPen pen;
- pen.setWidth(1);
- pen.setColor(underlineColor());
- painter.setPen(pen);
- painter.setOpacity(1);
- painter.drawLine(2, y, wd, y);
+ QPen pen;
+ pen.setWidth(1);
+ pen.setColor(underlineColor());
+ painter.setPen(pen);
+ painter.setOpacity(1);
+ painter.drawLine(2, y, wd, y);
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
- brush.setColor(inkColor());
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
+ brush.setColor(inkColor());
- const qreal progress = state_machine_->progress();
+ const qreal progress = state_machine_->progress();
- if (progress > 0) {
- painter.setPen(Qt::NoPen);
- painter.setBrush(brush);
- const int w = (1 - progress) * static_cast<qreal>(wd / 2);
- painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2);
- }
+ if (progress > 0) {
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(brush);
+ const int w = (1 - progress) * static_cast<qreal>(wd / 2);
+ painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2);
+ }
}
TextFieldStateMachine::TextFieldStateMachine(TextField *parent)
: QStateMachine(parent)
, text_field_(parent)
{
- normal_state_ = new QState;
- focused_state_ = new QState;
+ normal_state_ = new QState;
+ focused_state_ = new QState;
- label_ = nullptr;
- offset_anim_ = nullptr;
- color_anim_ = nullptr;
- progress_ = 0.0;
+ label_ = nullptr;
+ offset_anim_ = nullptr;
+ color_anim_ = nullptr;
+ progress_ = 0.0;
- addState(normal_state_);
- addState(focused_state_);
+ addState(normal_state_);
+ addState(focused_state_);
- setInitialState(normal_state_);
+ setInitialState(normal_state_);
- QEventTransition *transition;
- QPropertyAnimation *animation;
+ QEventTransition *transition;
+ QPropertyAnimation *animation;
- transition = new QEventTransition(parent, QEvent::FocusIn);
- transition->setTargetState(focused_state_);
- normal_state_->addTransition(transition);
+ transition = new QEventTransition(parent, QEvent::FocusIn);
+ transition->setTargetState(focused_state_);
+ normal_state_->addTransition(transition);
- animation = new QPropertyAnimation(this, "progress", this);
- animation->setEasingCurve(QEasingCurve::InCubic);
- animation->setDuration(310);
- transition->addAnimation(animation);
+ animation = new QPropertyAnimation(this, "progress", this);
+ animation->setEasingCurve(QEasingCurve::InCubic);
+ animation->setDuration(310);
+ transition->addAnimation(animation);
- transition = new QEventTransition(parent, QEvent::FocusOut);
- transition->setTargetState(normal_state_);
- focused_state_->addTransition(transition);
+ transition = new QEventTransition(parent, QEvent::FocusOut);
+ transition->setTargetState(normal_state_);
+ focused_state_->addTransition(transition);
- animation = new QPropertyAnimation(this, "progress", this);
- animation->setEasingCurve(QEasingCurve::OutCubic);
- animation->setDuration(310);
- transition->addAnimation(animation);
+ animation = new QPropertyAnimation(this, "progress", this);
+ animation->setEasingCurve(QEasingCurve::OutCubic);
+ animation->setDuration(310);
+ transition->addAnimation(animation);
- normal_state_->assignProperty(this, "progress", 0);
- focused_state_->assignProperty(this, "progress", 1);
+ normal_state_->assignProperty(this, "progress", 0);
+ focused_state_->assignProperty(this, "progress", 1);
- setupProperties();
+ setupProperties();
- connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties()));
+ connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties()));
}
void
TextFieldStateMachine::setLabel(TextFieldLabel *label)
{
- if (label_) {
- delete label_;
- }
-
- if (offset_anim_) {
- removeDefaultAnimation(offset_anim_);
- delete offset_anim_;
- }
-
- if (color_anim_) {
- removeDefaultAnimation(color_anim_);
- delete color_anim_;
- }
-
- label_ = label;
-
- if (label_) {
- offset_anim_ = new QPropertyAnimation(label_, "offset", this);
- offset_anim_->setDuration(210);
- offset_anim_->setEasingCurve(QEasingCurve::OutCubic);
- addDefaultAnimation(offset_anim_);
-
- color_anim_ = new QPropertyAnimation(label_, "color", this);
- color_anim_->setDuration(210);
- addDefaultAnimation(color_anim_);
- }
-
- setupProperties();
+ if (label_) {
+ delete label_;
+ }
+
+ if (offset_anim_) {
+ removeDefaultAnimation(offset_anim_);
+ delete offset_anim_;
+ }
+
+ if (color_anim_) {
+ removeDefaultAnimation(color_anim_);
+ delete color_anim_;
+ }
+
+ label_ = label;
+
+ if (label_) {
+ offset_anim_ = new QPropertyAnimation(label_, "offset", this);
+ offset_anim_->setDuration(210);
+ offset_anim_->setEasingCurve(QEasingCurve::OutCubic);
+ addDefaultAnimation(offset_anim_);
+
+ color_anim_ = new QPropertyAnimation(label_, "color", this);
+ color_anim_->setDuration(210);
+ addDefaultAnimation(color_anim_);
+ }
+
+ setupProperties();
}
void
TextFieldStateMachine::setupProperties()
{
- if (label_) {
- const int m = text_field_->textMargins().top();
-
- if (text_field_->text().isEmpty()) {
- normal_state_->assignProperty(label_, "offset", QPointF(0, 26));
- } else {
- normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
- }
-
- focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
- focused_state_->assignProperty(label_, "color", text_field_->inkColor());
- normal_state_->assignProperty(label_, "color", text_field_->labelColor());
-
- if (0 != label_->offset().y() && !text_field_->text().isEmpty()) {
- label_->setOffset(QPointF(0, 0 - m));
- } else if (!text_field_->hasFocus() && label_->offset().y() <= 0 &&
- text_field_->text().isEmpty()) {
- label_->setOffset(QPointF(0, 26));
- }
+ if (label_) {
+ const int m = text_field_->textMargins().top();
+
+ if (text_field_->text().isEmpty()) {
+ normal_state_->assignProperty(label_, "offset", QPointF(0, 26));
+ } else {
+ normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
+ }
+
+ focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
+ focused_state_->assignProperty(label_, "color", text_field_->inkColor());
+ normal_state_->assignProperty(label_, "color", text_field_->labelColor());
+
+ if (0 != label_->offset().y() && !text_field_->text().isEmpty()) {
+ label_->setOffset(QPointF(0, 0 - m));
+ } else if (!text_field_->hasFocus() && label_->offset().y() <= 0 &&
+ text_field_->text().isEmpty()) {
+ label_->setOffset(QPointF(0, 26));
}
+ }
- text_field_->update();
+ text_field_->update();
}
TextFieldLabel::TextFieldLabel(TextField *parent)
: QWidget(parent)
, text_field_(parent)
{
- x_ = 0;
- y_ = 26;
- scale_ = 1;
- color_ = parent->labelColor();
-
- QFont font;
- font.setWeight(60);
- font.setLetterSpacing(QFont::PercentageSpacing, 102);
- setFont(font);
+ x_ = 0;
+ y_ = 26;
+ scale_ = 1;
+ color_ = parent->labelColor();
+
+ QFont font;
+ font.setWeight(60);
+ font.setLetterSpacing(QFont::PercentageSpacing, 102);
+ setFont(font);
}
void
TextFieldLabel::paintEvent(QPaintEvent *)
{
- if (!text_field_->hasLabel())
- return;
+ if (!text_field_->hasLabel())
+ return;
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
- painter.scale(scale_, scale_);
- painter.setPen(color_);
- painter.setOpacity(1);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.scale(scale_, scale_);
+ painter.setPen(color_);
+ painter.setOpacity(1);
- QPointF pos(2 + x_, height() - 36 + y_);
- painter.drawText(pos.x(), pos.y(), text_field_->label());
+ QPointF pos(2 + x_, height() - 36 + y_);
+ painter.drawText(pos.x(), pos.y(), text_field_->label());
}
diff --git a/src/ui/TextField.h b/src/ui/TextField.h
index ac4c396e..47257019 100644
--- a/src/ui/TextField.h
+++ b/src/ui/TextField.h
@@ -18,163 +18,163 @@ class TextFieldStateMachine;
class TextField : public QLineEdit
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor)
- Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor)
- Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor)
- Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+ Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor)
+ Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor)
+ Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
public:
- explicit TextField(QWidget *parent = nullptr);
-
- void setInkColor(const QColor &color);
- void setBackgroundColor(const QColor &color);
- void setLabel(const QString &label);
- void setLabelColor(const QColor &color);
- void setLabelFontSize(qreal size);
- void setShowLabel(bool value);
- void setUnderlineColor(const QColor &color);
- void setRegexp(const QRegularExpression ®exp);
- void setValid(bool valid);
-
- QColor inkColor() const;
- QColor labelColor() const;
- QColor underlineColor() const;
- QColor backgroundColor() const;
- QString label() const;
- bool hasLabel() const;
- bool isValid() const;
- qreal labelFontSize() const;
+ explicit TextField(QWidget *parent = nullptr);
+
+ void setInkColor(const QColor &color);
+ void setBackgroundColor(const QColor &color);
+ void setLabel(const QString &label);
+ void setLabelColor(const QColor &color);
+ void setLabelFontSize(qreal size);
+ void setShowLabel(bool value);
+ void setUnderlineColor(const QColor &color);
+ void setRegexp(const QRegularExpression ®exp);
+ void setValid(bool valid);
+
+ QColor inkColor() const;
+ QColor labelColor() const;
+ QColor underlineColor() const;
+ QColor backgroundColor() const;
+ QString label() const;
+ bool hasLabel() const;
+ bool isValid() const;
+ qreal labelFontSize() const;
protected:
- bool event(QEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
+ bool event(QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
private:
- void init();
-
- QColor ink_color_;
- QColor background_color_;
- QColor label_color_;
- QColor underline_color_;
- QString label_text_;
- TextFieldLabel *label_;
- TextFieldStateMachine *state_machine_;
- bool show_label_;
- QRegularExpression regexp_;
- bool is_valid_;
- qreal label_font_size_;
+ void init();
+
+ QColor ink_color_;
+ QColor background_color_;
+ QColor label_color_;
+ QColor underline_color_;
+ QString label_text_;
+ TextFieldLabel *label_;
+ TextFieldStateMachine *state_machine_;
+ bool show_label_;
+ QRegularExpression regexp_;
+ bool is_valid_;
+ qreal label_font_size_;
};
class TextFieldLabel : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(qreal scale WRITE setScale READ scale)
- Q_PROPERTY(QPointF offset WRITE setOffset READ offset)
- Q_PROPERTY(QColor color WRITE setColor READ color)
+ Q_PROPERTY(qreal scale WRITE setScale READ scale)
+ Q_PROPERTY(QPointF offset WRITE setOffset READ offset)
+ Q_PROPERTY(QColor color WRITE setColor READ color)
public:
- TextFieldLabel(TextField *parent);
+ TextFieldLabel(TextField *parent);
- inline void setColor(const QColor &color);
- inline void setOffset(const QPointF &pos);
- inline void setScale(qreal scale);
+ inline void setColor(const QColor &color);
+ inline void setOffset(const QPointF &pos);
+ inline void setScale(qreal scale);
- inline QColor color() const;
- inline QPointF offset() const;
- inline qreal scale() const;
+ inline QColor color() const;
+ inline QPointF offset() const;
+ inline qreal scale() const;
protected:
- void paintEvent(QPaintEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
private:
- TextField *const text_field_;
+ TextField *const text_field_;
- QColor color_;
- qreal scale_;
- qreal x_;
- qreal y_;
+ QColor color_;
+ qreal scale_;
+ qreal x_;
+ qreal y_;
};
inline void
TextFieldLabel::setColor(const QColor &color)
{
- color_ = color;
- update();
+ color_ = color;
+ update();
}
inline void
TextFieldLabel::setOffset(const QPointF &pos)
{
- x_ = pos.x();
- y_ = pos.y();
- update();
+ x_ = pos.x();
+ y_ = pos.y();
+ update();
}
inline void
TextFieldLabel::setScale(qreal scale)
{
- scale_ = scale;
- update();
+ scale_ = scale;
+ update();
}
inline QPointF
TextFieldLabel::offset() const
{
- return QPointF(x_, y_);
+ return QPointF(x_, y_);
}
inline qreal
TextFieldLabel::scale() const
{
- return scale_;
+ return scale_;
}
inline QColor
TextFieldLabel::color() const
{
- return color_;
+ return color_;
}
class TextFieldStateMachine : public QStateMachine
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(qreal progress WRITE setProgress READ progress)
+ Q_PROPERTY(qreal progress WRITE setProgress READ progress)
public:
- TextFieldStateMachine(TextField *parent);
+ TextFieldStateMachine(TextField *parent);
- inline void setProgress(qreal progress);
- void setLabel(TextFieldLabel *label);
+ inline void setProgress(qreal progress);
+ void setLabel(TextFieldLabel *label);
- inline qreal progress() const;
+ inline qreal progress() const;
public slots:
- void setupProperties();
+ void setupProperties();
private:
- QPropertyAnimation *color_anim_;
- QPropertyAnimation *offset_anim_;
+ QPropertyAnimation *color_anim_;
+ QPropertyAnimation *offset_anim_;
- QState *focused_state_;
- QState *normal_state_;
+ QState *focused_state_;
+ QState *normal_state_;
- TextField *text_field_;
- TextFieldLabel *label_;
+ TextField *text_field_;
+ TextFieldLabel *label_;
- qreal progress_;
+ qreal progress_;
};
inline void
TextFieldStateMachine::setProgress(qreal progress)
{
- progress_ = progress;
- text_field_->update();
+ progress_ = progress;
+ text_field_->update();
}
inline qreal
TextFieldStateMachine::progress() const
{
- return progress_;
+ return progress_;
}
diff --git a/src/ui/TextLabel.cpp b/src/ui/TextLabel.cpp
index 3568e15c..340d3b8f 100644
--- a/src/ui/TextLabel.cpp
+++ b/src/ui/TextLabel.cpp
@@ -14,12 +14,12 @@
bool
ContextMenuFilter::eventFilter(QObject *obj, QEvent *event)
{
- if (event->type() == QEvent::MouseButtonPress) {
- emit contextMenuIsOpening();
- return true;
- }
+ if (event->type() == QEvent::MouseButtonPress) {
+ emit contextMenuIsOpening();
+ return true;
+ }
- return QObject::eventFilter(obj, event);
+ return QObject::eventFilter(obj, event);
}
TextLabel::TextLabel(QWidget *parent)
@@ -29,94 +29,94 @@ TextLabel::TextLabel(QWidget *parent)
TextLabel::TextLabel(const QString &text, QWidget *parent)
: QTextBrowser(parent)
{
- document()->setDefaultStyleSheet(QString("a {color: %1; }").arg(utils::linkColor()));
-
- setText(text);
- setOpenExternalLinks(true);
-
- // Make it look and feel like an ordinary label.
- setReadOnly(true);
- setFrameStyle(QFrame::NoFrame);
- QPalette pal = palette();
- pal.setColor(QPalette::Base, Qt::transparent);
- setPalette(pal);
-
- // Wrap anywhere but prefer words, adjust minimum height on the fly.
- setLineWrapMode(QTextEdit::WidgetWidth);
- setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
- connect(document()->documentLayout(),
- &QAbstractTextDocumentLayout::documentSizeChanged,
- this,
- &TextLabel::adjustHeight);
- document()->setDocumentMargin(0);
-
- setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
- setFixedHeight(0);
-
- connect(this, &TextLabel::linkActivated, this, &TextLabel::handleLinkActivation);
-
- auto filter = new ContextMenuFilter(this);
- installEventFilter(filter);
- connect(filter, &ContextMenuFilter::contextMenuIsOpening, this, [this]() {
- contextMenuRequested_ = true;
- });
+ document()->setDefaultStyleSheet(QString("a {color: %1; }").arg(utils::linkColor()));
+
+ setText(text);
+ setOpenExternalLinks(true);
+
+ // Make it look and feel like an ordinary label.
+ setReadOnly(true);
+ setFrameStyle(QFrame::NoFrame);
+ QPalette pal = palette();
+ pal.setColor(QPalette::Base, Qt::transparent);
+ setPalette(pal);
+
+ // Wrap anywhere but prefer words, adjust minimum height on the fly.
+ setLineWrapMode(QTextEdit::WidgetWidth);
+ setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+ connect(document()->documentLayout(),
+ &QAbstractTextDocumentLayout::documentSizeChanged,
+ this,
+ &TextLabel::adjustHeight);
+ document()->setDocumentMargin(0);
+
+ setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ setFixedHeight(0);
+
+ connect(this, &TextLabel::linkActivated, this, &TextLabel::handleLinkActivation);
+
+ auto filter = new ContextMenuFilter(this);
+ installEventFilter(filter);
+ connect(filter, &ContextMenuFilter::contextMenuIsOpening, this, [this]() {
+ contextMenuRequested_ = true;
+ });
}
void
TextLabel::focusOutEvent(QFocusEvent *e)
{
- QTextBrowser::focusOutEvent(e);
+ QTextBrowser::focusOutEvent(e);
- // We keep the selection available for the context menu.
- if (contextMenuRequested_) {
- contextMenuRequested_ = false;
- return;
- }
+ // We keep the selection available for the context menu.
+ if (contextMenuRequested_) {
+ contextMenuRequested_ = false;
+ return;
+ }
- QTextCursor cursor = textCursor();
- cursor.clearSelection();
- setTextCursor(cursor);
+ QTextCursor cursor = textCursor();
+ cursor.clearSelection();
+ setTextCursor(cursor);
}
void
TextLabel::mousePressEvent(QMouseEvent *e)
{
- link_ = (e->button() & Qt::LeftButton) ? anchorAt(e->pos()) : QString();
- QTextBrowser::mousePressEvent(e);
+ link_ = (e->button() & Qt::LeftButton) ? anchorAt(e->pos()) : QString();
+ QTextBrowser::mousePressEvent(e);
}
void
TextLabel::mouseReleaseEvent(QMouseEvent *e)
{
- if (e->button() & Qt::LeftButton && !link_.isEmpty() && anchorAt(e->pos()) == link_) {
- emit linkActivated(link_);
- return;
- }
+ if (e->button() & Qt::LeftButton && !link_.isEmpty() && anchorAt(e->pos()) == link_) {
+ emit linkActivated(link_);
+ return;
+ }
- QTextBrowser::mouseReleaseEvent(e);
+ QTextBrowser::mouseReleaseEvent(e);
}
void
TextLabel::wheelEvent(QWheelEvent *event)
{
- event->ignore();
+ event->ignore();
}
void
TextLabel::handleLinkActivation(const QUrl &url)
{
- auto parts = url.toString().split('/');
- auto defaultHandler = [](const QUrl &url) { QDesktopServices::openUrl(url); };
+ auto parts = url.toString().split('/');
+ auto defaultHandler = [](const QUrl &url) { QDesktopServices::openUrl(url); };
- if (url.host() != "matrix.to" || parts.isEmpty())
- return defaultHandler(url);
+ if (url.host() != "matrix.to" || parts.isEmpty())
+ return defaultHandler(url);
- try {
- using namespace mtx::identifiers;
- parse<User>(parts.last().toStdString());
- } catch (const std::exception &) {
- return defaultHandler(url);
- }
+ try {
+ using namespace mtx::identifiers;
+ parse<User>(parts.last().toStdString());
+ } catch (const std::exception &) {
+ return defaultHandler(url);
+ }
- emit userProfileTriggered(parts.last());
+ emit userProfileTriggered(parts.last());
}
diff --git a/src/ui/TextLabel.h b/src/ui/TextLabel.h
index bc095823..313ad97c 100644
--- a/src/ui/TextLabel.h
+++ b/src/ui/TextLabel.h
@@ -15,45 +15,45 @@ class QWheelEvent;
class ContextMenuFilter : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit ContextMenuFilter(QWidget *parent)
- : QObject(parent)
- {}
+ explicit ContextMenuFilter(QWidget *parent)
+ : QObject(parent)
+ {}
signals:
- void contextMenuIsOpening();
+ void contextMenuIsOpening();
protected:
- bool eventFilter(QObject *obj, QEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
};
class TextLabel : public QTextBrowser
{
- Q_OBJECT
+ Q_OBJECT
public:
- TextLabel(const QString &text, QWidget *parent = nullptr);
- TextLabel(QWidget *parent = nullptr);
+ TextLabel(const QString &text, QWidget *parent = nullptr);
+ TextLabel(QWidget *parent = nullptr);
- void wheelEvent(QWheelEvent *event) override;
- void clearLinks() { link_.clear(); }
+ void wheelEvent(QWheelEvent *event) override;
+ void clearLinks() { link_.clear(); }
protected:
- void mousePressEvent(QMouseEvent *e) override;
- void mouseReleaseEvent(QMouseEvent *e) override;
- void focusOutEvent(QFocusEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+ void focusOutEvent(QFocusEvent *e) override;
private slots:
- void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); }
- void handleLinkActivation(const QUrl &link);
+ void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); }
+ void handleLinkActivation(const QUrl &link);
signals:
- void userProfileTriggered(const QString &user_id);
- void linkActivated(const QUrl &link);
+ void userProfileTriggered(const QString &user_id);
+ void linkActivated(const QUrl &link);
private:
- QString link_;
- bool contextMenuRequested_ = false;
+ QString link_;
+ bool contextMenuRequested_ = false;
};
diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp
index 732a0443..d6f0b72f 100644
--- a/src/ui/Theme.cpp
+++ b/src/ui/Theme.cpp
@@ -9,66 +9,66 @@ Q_DECLARE_METATYPE(Theme)
QPalette
Theme::paletteFromTheme(std::string_view theme)
{
- [[maybe_unused]] static auto meta = qRegisterMetaType<Theme>("Theme");
- static QPalette original;
- if (theme == "light") {
- QPalette lightActive(
- /*windowText*/ QColor("#333"),
- /*button*/ QColor("white"),
- /*light*/ QColor(0xef, 0xef, 0xef),
- /*dark*/ QColor(70, 77, 93),
- /*mid*/ QColor(220, 220, 220),
- /*text*/ QColor("#333"),
- /*bright_text*/ QColor("#f2f5f8"),
- /*base*/ QColor("#fff"),
- /*window*/ QColor("white"));
- lightActive.setColor(QPalette::AlternateBase, QColor("#eee"));
- lightActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
- lightActive.setColor(QPalette::HighlightedText, QColor("#f4f4f5"));
- lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color());
- lightActive.setColor(QPalette::ToolTipText, lightActive.text().color());
- lightActive.setColor(QPalette::Link, QColor("#0077b5"));
- lightActive.setColor(QPalette::ButtonText, QColor("#555459"));
- return lightActive;
- } else if (theme == "dark") {
- QPalette darkActive(
- /*windowText*/ QColor("#caccd1"),
- /*button*/ QColor(0xff, 0xff, 0xff),
- /*light*/ QColor("#caccd1"),
- /*dark*/ QColor(60, 70, 77),
- /*mid*/ QColor("#202228"),
- /*text*/ QColor("#caccd1"),
- /*bright_text*/ QColor("#f4f5f8"),
- /*base*/ QColor("#202228"),
- /*window*/ QColor("#2d3139"));
- darkActive.setColor(QPalette::AlternateBase, QColor("#2d3139"));
- darkActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
- darkActive.setColor(QPalette::HighlightedText, QColor("#f4f5f8"));
- darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
- darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
- darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
- darkActive.setColor(QPalette::ButtonText, "#828284");
- return darkActive;
- } else {
- return original;
- }
+ [[maybe_unused]] static auto meta = qRegisterMetaType<Theme>("Theme");
+ static QPalette original;
+ if (theme == "light") {
+ QPalette lightActive(
+ /*windowText*/ QColor("#333"),
+ /*button*/ QColor("white"),
+ /*light*/ QColor(0xef, 0xef, 0xef),
+ /*dark*/ QColor(70, 77, 93),
+ /*mid*/ QColor(220, 220, 220),
+ /*text*/ QColor("#333"),
+ /*bright_text*/ QColor("#f2f5f8"),
+ /*base*/ QColor("#fff"),
+ /*window*/ QColor("white"));
+ lightActive.setColor(QPalette::AlternateBase, QColor("#eee"));
+ lightActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
+ lightActive.setColor(QPalette::HighlightedText, QColor("#f4f4f5"));
+ lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color());
+ lightActive.setColor(QPalette::ToolTipText, lightActive.text().color());
+ lightActive.setColor(QPalette::Link, QColor("#0077b5"));
+ lightActive.setColor(QPalette::ButtonText, QColor("#555459"));
+ return lightActive;
+ } else if (theme == "dark") {
+ QPalette darkActive(
+ /*windowText*/ QColor("#caccd1"),
+ /*button*/ QColor(0xff, 0xff, 0xff),
+ /*light*/ QColor("#caccd1"),
+ /*dark*/ QColor(60, 70, 77),
+ /*mid*/ QColor("#202228"),
+ /*text*/ QColor("#caccd1"),
+ /*bright_text*/ QColor("#f4f5f8"),
+ /*base*/ QColor("#202228"),
+ /*window*/ QColor("#2d3139"));
+ darkActive.setColor(QPalette::AlternateBase, QColor("#2d3139"));
+ darkActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
+ darkActive.setColor(QPalette::HighlightedText, QColor("#f4f5f8"));
+ darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
+ darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
+ darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
+ darkActive.setColor(QPalette::ButtonText, "#828284");
+ return darkActive;
+ } else {
+ return original;
+ }
}
Theme::Theme(std::string_view theme)
{
- auto p = paletteFromTheme(theme);
- separator_ = p.mid().color();
- if (theme == "light") {
- sidebarBackground_ = QColor("#233649");
- alternateButton_ = QColor("#ccc");
- red_ = QColor("#a82353");
- } else if (theme == "dark") {
- sidebarBackground_ = QColor("#2d3139");
- alternateButton_ = QColor("#414A59");
- red_ = QColor("#a82353");
- } else {
- sidebarBackground_ = p.window().color();
- alternateButton_ = p.dark().color();
- red_ = QColor("red");
- }
+ auto p = paletteFromTheme(theme);
+ separator_ = p.mid().color();
+ if (theme == "light") {
+ sidebarBackground_ = QColor("#233649");
+ alternateButton_ = QColor("#ccc");
+ red_ = QColor("#a82353");
+ } else if (theme == "dark") {
+ sidebarBackground_ = QColor("#2d3139");
+ alternateButton_ = QColor("#414A59");
+ red_ = QColor("#a82353");
+ } else {
+ sidebarBackground_ = p.window().color();
+ alternateButton_ = p.dark().color();
+ red_ = QColor("red");
+ }
}
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index cc39714b..f3e7c287 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -16,62 +16,62 @@ const int AvatarSize = 40;
enum class ButtonPreset
{
- FlatPreset,
- CheckablePreset
+ FlatPreset,
+ CheckablePreset
};
enum class RippleStyle
{
- CenteredRipple,
- PositionedRipple,
- NoRipple
+ CenteredRipple,
+ PositionedRipple,
+ NoRipple
};
enum class OverlayStyle
{
- NoOverlay,
- TintedOverlay,
- GrayOverlay
+ NoOverlay,
+ TintedOverlay,
+ GrayOverlay
};
enum class Role
{
- Default,
- Primary,
- Secondary
+ Default,
+ Primary,
+ Secondary
};
enum class ButtonIconPlacement
{
- LeftIcon,
- RightIcon
+ LeftIcon,
+ RightIcon
};
enum class ProgressType
{
- DeterminateProgress,
- IndeterminateProgress
+ DeterminateProgress,
+ IndeterminateProgress
};
} // namespace ui
class Theme : public QPalette
{
- Q_GADGET
- Q_PROPERTY(QColor sidebarBackground READ sidebarBackground CONSTANT)
- Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT)
- Q_PROPERTY(QColor separator READ separator CONSTANT)
- Q_PROPERTY(QColor red READ red CONSTANT)
+ Q_GADGET
+ Q_PROPERTY(QColor sidebarBackground READ sidebarBackground CONSTANT)
+ Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT)
+ Q_PROPERTY(QColor separator READ separator CONSTANT)
+ Q_PROPERTY(QColor red READ red CONSTANT)
public:
- Theme() {}
- explicit Theme(std::string_view theme);
- static QPalette paletteFromTheme(std::string_view theme);
+ Theme() {}
+ explicit Theme(std::string_view theme);
+ static QPalette paletteFromTheme(std::string_view theme);
- QColor sidebarBackground() const { return sidebarBackground_; }
- QColor alternateButton() const { return alternateButton_; }
- QColor separator() const { return separator_; }
- QColor red() const { return red_; }
+ QColor sidebarBackground() const { return sidebarBackground_; }
+ QColor alternateButton() const { return alternateButton_; }
+ QColor separator() const { return separator_; }
+ QColor red() const { return red_; }
private:
- QColor sidebarBackground_, separator_, red_, alternateButton_;
+ QColor sidebarBackground_, separator_, red_, alternateButton_;
};
diff --git a/src/ui/ThemeManager.cpp b/src/ui/ThemeManager.cpp
index b7b3df40..0c84777a 100644
--- a/src/ui/ThemeManager.cpp
+++ b/src/ui/ThemeManager.cpp
@@ -11,32 +11,32 @@ ThemeManager::ThemeManager() {}
QColor
ThemeManager::themeColor(const QString &key) const
{
- if (key == "Black")
- return QColor("#171919");
-
- else if (key == "BrightWhite")
- return QColor("#EBEBEB");
- else if (key == "FadedWhite")
- return QColor("#C9C9C9");
- else if (key == "MediumWhite")
- return QColor("#929292");
-
- else if (key == "BrightGreen")
- return QColor("#1C3133");
- else if (key == "DarkGreen")
- return QColor("#577275");
- else if (key == "LightGreen")
- return QColor("#46A451");
-
- else if (key == "Gray")
- return QColor("#5D6565");
- else if (key == "Red")
- return QColor("#E22826");
- else if (key == "Blue")
- return QColor("#81B3A9");
-
- else if (key == "Transparent")
- return QColor(0, 0, 0, 0);
-
- return (QColor(0, 0, 0, 0));
+ if (key == "Black")
+ return QColor("#171919");
+
+ else if (key == "BrightWhite")
+ return QColor("#EBEBEB");
+ else if (key == "FadedWhite")
+ return QColor("#C9C9C9");
+ else if (key == "MediumWhite")
+ return QColor("#929292");
+
+ else if (key == "BrightGreen")
+ return QColor("#1C3133");
+ else if (key == "DarkGreen")
+ return QColor("#577275");
+ else if (key == "LightGreen")
+ return QColor("#46A451");
+
+ else if (key == "Gray")
+ return QColor("#5D6565");
+ else if (key == "Red")
+ return QColor("#E22826");
+ else if (key == "Blue")
+ return QColor("#81B3A9");
+
+ else if (key == "Transparent")
+ return QColor(0, 0, 0, 0);
+
+ return (QColor(0, 0, 0, 0));
}
diff --git a/src/ui/ThemeManager.h b/src/ui/ThemeManager.h
index cbb355fd..5e86c68f 100644
--- a/src/ui/ThemeManager.h
+++ b/src/ui/ThemeManager.h
@@ -8,23 +8,23 @@
class ThemeManager : public QCommonStyle
{
- Q_OBJECT
+ Q_OBJECT
public:
- inline static ThemeManager &instance();
+ inline static ThemeManager &instance();
- QColor themeColor(const QString &key) const;
+ QColor themeColor(const QString &key) const;
private:
- ThemeManager();
+ ThemeManager();
- ThemeManager(ThemeManager const &);
- void operator=(ThemeManager const &);
+ ThemeManager(ThemeManager const &);
+ void operator=(ThemeManager const &);
};
inline ThemeManager &
ThemeManager::instance()
{
- static ThemeManager instance;
- return instance;
+ static ThemeManager instance;
+ return instance;
}
diff --git a/src/ui/ToggleButton.cpp b/src/ui/ToggleButton.cpp
index 33bf8f92..04f752d7 100644
--- a/src/ui/ToggleButton.cpp
+++ b/src/ui/ToggleButton.cpp
@@ -12,84 +12,84 @@
void
Toggle::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
}
Toggle::Toggle(QWidget *parent)
: QAbstractButton{parent}
{
- init();
+ init();
- connect(this, &QAbstractButton::toggled, this, &Toggle::setState);
+ connect(this, &QAbstractButton::toggled, this, &Toggle::setState);
}
void
Toggle::setState(bool isEnabled)
{
- setChecked(isEnabled);
- thumb_->setShift(isEnabled ? Position::Left : Position::Right);
- setupProperties();
+ setChecked(isEnabled);
+ thumb_->setShift(isEnabled ? Position::Left : Position::Right);
+ setupProperties();
}
void
Toggle::init()
{
- track_ = new ToggleTrack(this);
- thumb_ = new ToggleThumb(this);
+ track_ = new ToggleTrack(this);
+ thumb_ = new ToggleThumb(this);
- setCursor(QCursor(Qt::PointingHandCursor));
- setCheckable(true);
- setChecked(false);
+ setCursor(QCursor(Qt::PointingHandCursor));
+ setCheckable(true);
+ setChecked(false);
- setState(false);
- setupProperties();
+ setState(false);
+ setupProperties();
- QCoreApplication::processEvents();
+ QCoreApplication::processEvents();
}
void
Toggle::setupProperties()
{
- if (isEnabled()) {
- Position position = thumb_->shift();
+ if (isEnabled()) {
+ Position position = thumb_->shift();
- thumb_->setThumbColor(trackColor());
+ thumb_->setThumbColor(trackColor());
- if (position == Position::Left)
- track_->setTrackColor(activeColor());
- else if (position == Position::Right)
- track_->setTrackColor(inactiveColor());
- }
+ if (position == Position::Left)
+ track_->setTrackColor(activeColor());
+ else if (position == Position::Right)
+ track_->setTrackColor(inactiveColor());
+ }
- update();
+ update();
}
void
Toggle::setDisabledColor(const QColor &color)
{
- disabledColor_ = color;
- setupProperties();
+ disabledColor_ = color;
+ setupProperties();
}
void
Toggle::setActiveColor(const QColor &color)
{
- activeColor_ = color;
- setupProperties();
+ activeColor_ = color;
+ setupProperties();
}
void
Toggle::setInactiveColor(const QColor &color)
{
- inactiveColor_ = color;
- setupProperties();
+ inactiveColor_ = color;
+ setupProperties();
}
void
Toggle::setTrackColor(const QColor &color)
{
- trackColor_ = color;
- setupProperties();
+ trackColor_ = color;
+ setupProperties();
}
ToggleThumb::ToggleThumb(Toggle *parent)
@@ -98,119 +98,119 @@ ToggleThumb::ToggleThumb(Toggle *parent)
, position_{Position::Right}
, offset_{0}
{
- parent->installEventFilter(this);
+ parent->installEventFilter(this);
}
void
ToggleThumb::setShift(Position position)
{
- if (position_ != position) {
- position_ = position;
- updateOffset();
- }
+ if (position_ != position) {
+ position_ = position;
+ updateOffset();
+ }
}
bool
ToggleThumb::eventFilter(QObject *obj, QEvent *event)
{
- const QEvent::Type type = event->type();
+ const QEvent::Type type = event->type();
- if (QEvent::Resize == type || QEvent::Move == type) {
- setGeometry(toggle_->rect().adjusted(8, 8, -8, -8));
- updateOffset();
- }
+ if (QEvent::Resize == type || QEvent::Move == type) {
+ setGeometry(toggle_->rect().adjusted(8, 8, -8, -8));
+ updateOffset();
+ }
- return QWidget::eventFilter(obj, event);
+ return QWidget::eventFilter(obj, event);
}
void
ToggleThumb::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event)
+ Q_UNUSED(event)
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
- brush.setColor(toggle_->isEnabled() ? thumbColor_ : Qt::white);
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
+ brush.setColor(toggle_->isEnabled() ? thumbColor_ : Qt::white);
- painter.setBrush(brush);
- painter.setPen(Qt::NoPen);
+ painter.setBrush(brush);
+ painter.setPen(Qt::NoPen);
- int s;
- QRectF r;
+ int s;
+ QRectF r;
- s = height() - 10;
- r = QRectF(5 + offset_, 5, s, s);
+ s = height() - 10;
+ r = QRectF(5 + offset_, 5, s, s);
- painter.drawEllipse(r);
+ painter.drawEllipse(r);
- if (!toggle_->isEnabled()) {
- brush.setColor(toggle_->disabledColor());
- painter.setBrush(brush);
- painter.drawEllipse(r);
- }
+ if (!toggle_->isEnabled()) {
+ brush.setColor(toggle_->disabledColor());
+ painter.setBrush(brush);
+ painter.drawEllipse(r);
+ }
}
void
ToggleThumb::updateOffset()
{
- const QSize s(size());
- offset_ = position_ == Position::Left ? static_cast<qreal>(s.width() - s.height()) : 0;
- update();
+ const QSize s(size());
+ offset_ = position_ == Position::Left ? static_cast<qreal>(s.width() - s.height()) : 0;
+ update();
}
ToggleTrack::ToggleTrack(Toggle *parent)
: QWidget{parent}
, toggle_{parent}
{
- Q_ASSERT(parent);
+ Q_ASSERT(parent);
- parent->installEventFilter(this);
+ parent->installEventFilter(this);
}
void
ToggleTrack::setTrackColor(const QColor &color)
{
- trackColor_ = color;
- update();
+ trackColor_ = color;
+ update();
}
bool
ToggleTrack::eventFilter(QObject *obj, QEvent *event)
{
- const QEvent::Type type = event->type();
+ const QEvent::Type type = event->type();
- if (QEvent::Resize == type || QEvent::Move == type) {
- setGeometry(toggle_->rect());
- }
+ if (QEvent::Resize == type || QEvent::Move == type) {
+ setGeometry(toggle_->rect());
+ }
- return QWidget::eventFilter(obj, event);
+ return QWidget::eventFilter(obj, event);
}
void
ToggleTrack::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event)
+ Q_UNUSED(event)
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
- QBrush brush;
- if (toggle_->isEnabled()) {
- brush.setColor(trackColor_);
- painter.setOpacity(0.8);
- } else {
- brush.setColor(toggle_->disabledColor());
- painter.setOpacity(0.6);
- }
+ QBrush brush;
+ if (toggle_->isEnabled()) {
+ brush.setColor(trackColor_);
+ painter.setOpacity(0.8);
+ } else {
+ brush.setColor(toggle_->disabledColor());
+ painter.setOpacity(0.6);
+ }
- brush.setStyle(Qt::SolidPattern);
- painter.setBrush(brush);
- painter.setPen(Qt::NoPen);
+ brush.setStyle(Qt::SolidPattern);
+ painter.setBrush(brush);
+ painter.setPen(Qt::NoPen);
- const int h = height() / 2;
- const QRect r(0, h / 2, width(), h);
- painter.drawRoundedRect(r.adjusted(14, 4, -14, -4), h / 2 - 4, h / 2 - 4);
+ const int h = height() / 2;
+ const QRect r(0, h / 2, width(), h);
+ painter.drawRoundedRect(r.adjusted(14, 4, -14, -4), h / 2 - 4, h / 2 - 4);
}
diff --git a/src/ui/ToggleButton.h b/src/ui/ToggleButton.h
index 2413b086..15d5e192 100644
--- a/src/ui/ToggleButton.h
+++ b/src/ui/ToggleButton.h
@@ -12,103 +12,103 @@ class ToggleThumb;
enum class Position
{
- Left,
- Right
+ Left,
+ Right
};
class Toggle : public QAbstractButton
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor activeColor WRITE setActiveColor READ activeColor)
- Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor)
- Q_PROPERTY(QColor inactiveColor WRITE setInactiveColor READ inactiveColor)
- Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
+ Q_PROPERTY(QColor activeColor WRITE setActiveColor READ activeColor)
+ Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor)
+ Q_PROPERTY(QColor inactiveColor WRITE setInactiveColor READ inactiveColor)
+ Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
public:
- Toggle(QWidget *parent = nullptr);
+ Toggle(QWidget *parent = nullptr);
- void setState(bool isEnabled);
+ void setState(bool isEnabled);
- void setActiveColor(const QColor &color);
- void setDisabledColor(const QColor &color);
- void setInactiveColor(const QColor &color);
- void setTrackColor(const QColor &color);
+ void setActiveColor(const QColor &color);
+ void setDisabledColor(const QColor &color);
+ void setInactiveColor(const QColor &color);
+ void setTrackColor(const QColor &color);
- QColor activeColor() const { return activeColor_; };
- QColor disabledColor() const { return disabledColor_; };
- QColor inactiveColor() const { return inactiveColor_; };
- QColor trackColor() const { return trackColor_.isValid() ? trackColor_ : QColor("#eee"); };
+ QColor activeColor() const { return activeColor_; };
+ QColor disabledColor() const { return disabledColor_; };
+ QColor inactiveColor() const { return inactiveColor_; };
+ QColor trackColor() const { return trackColor_.isValid() ? trackColor_ : QColor("#eee"); };
- QSize sizeHint() const override { return QSize(64, 48); };
+ QSize sizeHint() const override { return QSize(64, 48); };
protected:
- void paintEvent(QPaintEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
private:
- void init();
- void setupProperties();
+ void init();
+ void setupProperties();
- ToggleTrack *track_;
- ToggleThumb *thumb_;
+ ToggleTrack *track_;
+ ToggleThumb *thumb_;
- QColor disabledColor_;
- QColor activeColor_;
- QColor inactiveColor_;
- QColor trackColor_;
+ QColor disabledColor_;
+ QColor activeColor_;
+ QColor inactiveColor_;
+ QColor trackColor_;
};
class ToggleThumb : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor)
+ Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor)
public:
- ToggleThumb(Toggle *parent);
+ ToggleThumb(Toggle *parent);
- Position shift() const { return position_; };
- qreal offset() const { return offset_; };
- QColor thumbColor() const { return thumbColor_; };
+ Position shift() const { return position_; };
+ qreal offset() const { return offset_; };
+ QColor thumbColor() const { return thumbColor_; };
- void setShift(Position position);
- void setThumbColor(const QColor &color)
- {
- thumbColor_ = color;
- update();
- };
+ void setShift(Position position);
+ void setThumbColor(const QColor &color)
+ {
+ thumbColor_ = color;
+ update();
+ };
protected:
- bool eventFilter(QObject *obj, QEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
private:
- void updateOffset();
+ void updateOffset();
- Toggle *const toggle_;
- QColor thumbColor_;
+ Toggle *const toggle_;
+ QColor thumbColor_;
- Position position_;
- qreal offset_;
+ Position position_;
+ qreal offset_;
};
class ToggleTrack : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
- Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
+ Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
public:
- ToggleTrack(Toggle *parent);
+ ToggleTrack(Toggle *parent);
- void setTrackColor(const QColor &color);
- QColor trackColor() const { return trackColor_; };
+ void setTrackColor(const QColor &color);
+ QColor trackColor() const { return trackColor_; };
protected:
- bool eventFilter(QObject *obj, QEvent *event) override;
- void paintEvent(QPaintEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
private:
- Toggle *const toggle_;
- QColor trackColor_;
+ Toggle *const toggle_;
+ QColor trackColor_;
};
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index fbd0f4f7..58150ed7 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -27,210 +27,200 @@ UserProfile::UserProfile(QString roomid,
, manager(manager_)
, model(parent)
{
- globalAvatarUrl = "";
-
- connect(this,
- &UserProfile::globalUsernameRetrieved,
- this,
- &UserProfile::setGlobalUsername,
- Qt::QueuedConnection);
-
- if (isGlobalUserProfile()) {
- getGlobalProfileData();
- }
-
- if (!cache::client() || !cache::client()->isDatabaseReady() ||
- !ChatPage::instance()->timelineManager())
- return;
-
- connect(cache::client(),
- &Cache::verificationStatusChanged,
- this,
- [this](const std::string &user_id) {
- if (user_id != this->userid_.toStdString())
- return;
-
- auto status = cache::verificationStatus(user_id);
- if (!status)
- return;
- this->isUserVerified = status->user_verified;
- emit userStatusChanged();
-
- for (auto &deviceInfo : deviceList_.deviceList_) {
- deviceInfo.verification_status =
- std::find(status->verified_devices.begin(),
- status->verified_devices.end(),
- deviceInfo.device_id.toStdString()) ==
- status->verified_devices.end()
- ? verification::UNVERIFIED
- : verification::VERIFIED;
- }
- deviceList_.reset(deviceList_.deviceList_);
- emit devicesChanged();
- });
- fetchDeviceList(this->userid_);
+ globalAvatarUrl = "";
+
+ connect(this,
+ &UserProfile::globalUsernameRetrieved,
+ this,
+ &UserProfile::setGlobalUsername,
+ Qt::QueuedConnection);
+
+ if (isGlobalUserProfile()) {
+ getGlobalProfileData();
+ }
+
+ if (!cache::client() || !cache::client()->isDatabaseReady() ||
+ !ChatPage::instance()->timelineManager())
+ return;
+
+ connect(
+ cache::client(), &Cache::verificationStatusChanged, this, [this](const std::string &user_id) {
+ if (user_id != this->userid_.toStdString())
+ return;
+
+ auto status = cache::verificationStatus(user_id);
+ if (!status)
+ return;
+ this->isUserVerified = status->user_verified;
+ emit userStatusChanged();
+
+ for (auto &deviceInfo : deviceList_.deviceList_) {
+ deviceInfo.verification_status =
+ std::find(status->verified_devices.begin(),
+ status->verified_devices.end(),
+ deviceInfo.device_id.toStdString()) == status->verified_devices.end()
+ ? verification::UNVERIFIED
+ : verification::VERIFIED;
+ }
+ deviceList_.reset(deviceList_.deviceList_);
+ emit devicesChanged();
+ });
+ fetchDeviceList(this->userid_);
}
QHash<int, QByteArray>
DeviceInfoModel::roleNames() const
{
- return {
- {DeviceId, "deviceId"},
- {DeviceName, "deviceName"},
- {VerificationStatus, "verificationStatus"},
- };
+ return {
+ {DeviceId, "deviceId"},
+ {DeviceName, "deviceName"},
+ {VerificationStatus, "verificationStatus"},
+ };
}
QVariant
DeviceInfoModel::data(const QModelIndex &index, int role) const
{
- if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0)
- return {};
-
- switch (role) {
- case DeviceId:
- return deviceList_[index.row()].device_id;
- case DeviceName:
- return deviceList_[index.row()].display_name;
- case VerificationStatus:
- return QVariant::fromValue(deviceList_[index.row()].verification_status);
- default:
- return {};
- }
+ if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0)
+ return {};
+
+ switch (role) {
+ case DeviceId:
+ return deviceList_[index.row()].device_id;
+ case DeviceName:
+ return deviceList_[index.row()].display_name;
+ case VerificationStatus:
+ return QVariant::fromValue(deviceList_[index.row()].verification_status);
+ default:
+ return {};
+ }
}
void
DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList)
{
- beginResetModel();
- this->deviceList_ = std::move(deviceList);
- endResetModel();
+ beginResetModel();
+ this->deviceList_ = std::move(deviceList);
+ endResetModel();
}
DeviceInfoModel *
UserProfile::deviceList()
{
- return &this->deviceList_;
+ return &this->deviceList_;
}
QString
UserProfile::userid()
{
- return this->userid_;
+ return this->userid_;
}
QString
UserProfile::displayName()
{
- return isGlobalUserProfile() ? globalUsername : cache::displayName(roomid_, userid_);
+ return isGlobalUserProfile() ? globalUsername : cache::displayName(roomid_, userid_);
}
QString
UserProfile::avatarUrl()
{
- return isGlobalUserProfile() ? globalAvatarUrl : cache::avatarUrl(roomid_, userid_);
+ return isGlobalUserProfile() ? globalAvatarUrl : cache::avatarUrl(roomid_, userid_);
}
bool
UserProfile::isGlobalUserProfile() const
{
- return roomid_ == "";
+ return roomid_ == "";
}
crypto::Trust
UserProfile::getUserStatus()
{
- return isUserVerified;
+ return isUserVerified;
}
bool
UserProfile::userVerificationEnabled() const
{
- return hasMasterKey;
+ return hasMasterKey;
}
bool
UserProfile::isSelf() const
{
- return this->userid_ == utils::localUser();
+ return this->userid_ == utils::localUser();
}
void
UserProfile::fetchDeviceList(const QString &userID)
{
- auto localUser = utils::localUser();
-
- if (!cache::client() || !cache::client()->isDatabaseReady())
- return;
-
- cache::client()->query_keys(
- userID.toStdString(),
- [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to query device keys: {},{}",
- mtx::errors::to_string(err->matrix_error.errcode),
- static_cast<int>(err->status_code));
- return;
- }
-
- // Ensure local key cache is up to date
- cache::client()->query_keys(
- utils::localUser().toStdString(),
- [other_user_id, other_user_keys, this](const UserKeyCache &,
- mtx::http::RequestErr err) {
- using namespace mtx;
- std::string local_user_id = utils::localUser().toStdString();
-
- if (err) {
- nhlog::net()->warn(
- "failed to query device keys: {},{}",
- mtx::errors::to_string(err->matrix_error.errcode),
- static_cast<int>(err->status_code));
- return;
- }
-
- this->hasMasterKey = !other_user_keys.master_keys.keys.empty();
-
- std::vector<DeviceInfo> deviceInfo;
- auto devices = other_user_keys.device_keys;
- auto verificationStatus =
- cache::client()->verificationStatus(other_user_id);
-
- isUserVerified = verificationStatus.user_verified;
- emit userStatusChanged();
-
- for (const auto &d : devices) {
- auto device = d.second;
- verification::Status verified =
- verification::Status::UNVERIFIED;
-
- if (std::find(verificationStatus.verified_devices.begin(),
- verificationStatus.verified_devices.end(),
- device.device_id) !=
- verificationStatus.verified_devices.end() &&
- mtx::crypto::verify_identity_signature(
- device,
- DeviceId(device.device_id),
- UserId(other_user_id)))
- verified = verification::Status::VERIFIED;
-
- deviceInfo.push_back(
- {QString::fromStdString(d.first),
- QString::fromStdString(
- device.unsigned_info.device_display_name),
- verified});
- }
-
- this->deviceList_.queueReset(std::move(deviceInfo));
- emit devicesChanged();
- });
- });
+ auto localUser = utils::localUser();
+
+ if (!cache::client() || !cache::client()->isDatabaseReady())
+ return;
+
+ cache::client()->query_keys(
+ userID.toStdString(),
+ [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to query device keys: {},{}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ // Ensure local key cache is up to date
+ cache::client()->query_keys(
+ utils::localUser().toStdString(),
+ [other_user_id, other_user_keys, this](const UserKeyCache &,
+ mtx::http::RequestErr err) {
+ using namespace mtx;
+ std::string local_user_id = utils::localUser().toStdString();
+
+ if (err) {
+ nhlog::net()->warn("failed to query device keys: {},{}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ this->hasMasterKey = !other_user_keys.master_keys.keys.empty();
+
+ std::vector<DeviceInfo> deviceInfo;
+ auto devices = other_user_keys.device_keys;
+ auto verificationStatus = cache::client()->verificationStatus(other_user_id);
+
+ isUserVerified = verificationStatus.user_verified;
+ emit userStatusChanged();
+
+ for (const auto &d : devices) {
+ auto device = d.second;
+ verification::Status verified = verification::Status::UNVERIFIED;
+
+ if (std::find(verificationStatus.verified_devices.begin(),
+ verificationStatus.verified_devices.end(),
+ device.device_id) != verificationStatus.verified_devices.end() &&
+ mtx::crypto::verify_identity_signature(
+ device, DeviceId(device.device_id), UserId(other_user_id)))
+ verified = verification::Status::VERIFIED;
+
+ deviceInfo.push_back(
+ {QString::fromStdString(d.first),
+ QString::fromStdString(device.unsigned_info.device_display_name),
+ verified});
+ }
+
+ this->deviceList_.queueReset(std::move(deviceInfo));
+ emit devicesChanged();
+ });
+ });
}
void
UserProfile::banUser()
{
- ChatPage::instance()->banUser(this->userid_, "");
+ ChatPage::instance()->banUser(this->userid_, "");
}
// void ignoreUser(){
@@ -240,188 +230,180 @@ UserProfile::banUser()
void
UserProfile::kickUser()
{
- ChatPage::instance()->kickUser(this->userid_, "");
+ ChatPage::instance()->kickUser(this->userid_, "");
}
void
UserProfile::startChat()
{
- ChatPage::instance()->startChat(this->userid_);
+ ChatPage::instance()->startChat(this->userid_);
}
void
UserProfile::changeUsername(QString username)
{
- if (isGlobalUserProfile()) {
- // change global
- http::client()->set_displayname(
- username.toStdString(), [](mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("could not change username");
- return;
- }
- });
- } else {
- // change room username
- mtx::events::state::Member member;
- member.display_name = username.toStdString();
- member.avatar_url =
- cache::avatarUrl(roomid_,
- QString::fromStdString(http::client()->user_id().to_string()))
- .toStdString();
- member.membership = mtx::events::state::Membership::Join;
-
- updateRoomMemberState(std::move(member));
- }
+ if (isGlobalUserProfile()) {
+ // change global
+ http::client()->set_displayname(username.toStdString(), [](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("could not change username");
+ return;
+ }
+ });
+ } else {
+ // change room username
+ mtx::events::state::Member member;
+ member.display_name = username.toStdString();
+ member.avatar_url =
+ cache::avatarUrl(roomid_, QString::fromStdString(http::client()->user_id().to_string()))
+ .toStdString();
+ member.membership = mtx::events::state::Membership::Join;
+
+ updateRoomMemberState(std::move(member));
+ }
}
void
UserProfile::verify(QString device)
{
- if (!device.isEmpty())
- manager->verifyDevice(userid_, device);
- else {
- manager->verifyUser(userid_);
- }
+ if (!device.isEmpty())
+ manager->verifyDevice(userid_, device);
+ else {
+ manager->verifyUser(userid_);
+ }
}
void
UserProfile::unverify(QString device)
{
- cache::markDeviceUnverified(userid_.toStdString(), device.toStdString());
+ cache::markDeviceUnverified(userid_.toStdString(), device.toStdString());
}
void
UserProfile::setGlobalUsername(const QString &globalUser)
{
- globalUsername = globalUser;
- emit displayNameChanged();
+ globalUsername = globalUser;
+ emit displayNameChanged();
}
void
UserProfile::changeAvatar()
{
- const QString picturesFolder =
- QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
- const QString fileName = QFileDialog::getOpenFileName(
- nullptr, tr("Select an avatar"), picturesFolder, tr("All Files (*)"));
-
- if (fileName.isEmpty())
- return;
-
- QMimeDatabase db;
- QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
-
- const auto format = mime.name().split("/")[0];
-
- QFile file{fileName, this};
- if (format != "image") {
- emit displayError(tr("The selected file is not an image"));
- return;
- }
-
- if (!file.open(QIODevice::ReadOnly)) {
- emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
- return;
- }
-
- const auto bin = file.peek(file.size());
- const auto payload = std::string(bin.data(), bin.size());
-
- isLoading_ = true;
- emit loadingChanged();
-
- // First we need to create a new mxc URI
- // (i.e upload media to the Matrix content repository) for the new avatar.
- http::client()->upload(
- payload,
- mime.name().toStdString(),
- QFileInfo(fileName).fileName().toStdString(),
- [this,
- payload,
- mimetype = mime.name().toStdString(),
- size = payload.size(),
- room_id = roomid_.toStdString(),
- content = std::move(bin)](const mtx::responses::ContentURI &res,
- mtx::http::RequestErr err) {
+ const QString picturesFolder =
+ QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+ const QString fileName = QFileDialog::getOpenFileName(
+ nullptr, tr("Select an avatar"), picturesFolder, tr("All Files (*)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
+
+ const auto format = mime.name().split("/")[0];
+
+ QFile file{fileName, this};
+ if (format != "image") {
+ emit displayError(tr("The selected file is not an image"));
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
+ return;
+ }
+
+ const auto bin = file.peek(file.size());
+ const auto payload = std::string(bin.data(), bin.size());
+
+ isLoading_ = true;
+ emit loadingChanged();
+
+ // First we need to create a new mxc URI
+ // (i.e upload media to the Matrix content repository) for the new avatar.
+ http::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fileName).fileName().toStdString(),
+ [this,
+ payload,
+ mimetype = mime.name().toStdString(),
+ size = payload.size(),
+ room_id = roomid_.toStdString(),
+ content = std::move(bin)](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::ui()->error("Failed to upload image", err->matrix_error.error);
+ return;
+ }
+
+ if (isGlobalUserProfile()) {
+ http::client()->set_avatar_url(res.content_uri, [this](mtx::http::RequestErr err) {
if (err) {
- nhlog::ui()->error("Failed to upload image", err->matrix_error.error);
- return;
+ nhlog::ui()->error("Failed to set user avatar url", err->matrix_error.error);
}
- if (isGlobalUserProfile()) {
- http::client()->set_avatar_url(
- res.content_uri, [this](mtx::http::RequestErr err) {
- if (err) {
- nhlog::ui()->error("Failed to set user avatar url",
- err->matrix_error.error);
- }
-
- isLoading_ = false;
- emit loadingChanged();
- getGlobalProfileData();
- });
- } else {
- // change room username
- mtx::events::state::Member member;
- member.display_name = cache::displayName(roomid_, userid_).toStdString();
- member.avatar_url = res.content_uri;
- member.membership = mtx::events::state::Membership::Join;
-
- updateRoomMemberState(std::move(member));
- }
- });
+ isLoading_ = false;
+ emit loadingChanged();
+ getGlobalProfileData();
+ });
+ } else {
+ // change room username
+ mtx::events::state::Member member;
+ member.display_name = cache::displayName(roomid_, userid_).toStdString();
+ member.avatar_url = res.content_uri;
+ member.membership = mtx::events::state::Membership::Join;
+
+ updateRoomMemberState(std::move(member));
+ }
+ });
}
void
UserProfile::updateRoomMemberState(mtx::events::state::Member member)
{
- http::client()->send_state_event(roomid_.toStdString(),
- http::client()->user_id().to_string(),
- member,
- [](mtx::responses::EventId, mtx::http::RequestErr err) {
- if (err)
- nhlog::net()->error(
- "Failed to update room member state : ",
- err->matrix_error.error);
- });
+ http::client()->send_state_event(
+ roomid_.toStdString(),
+ http::client()->user_id().to_string(),
+ member,
+ [](mtx::responses::EventId, mtx::http::RequestErr err) {
+ if (err)
+ nhlog::net()->error("Failed to update room member state : ", err->matrix_error.error);
+ });
}
void
UserProfile::updateAvatarUrl()
{
- isLoading_ = false;
- emit loadingChanged();
+ isLoading_ = false;
+ emit loadingChanged();
- emit avatarUrlChanged();
+ emit avatarUrlChanged();
}
bool
UserProfile::isLoading() const
{
- return isLoading_;
+ return isLoading_;
}
void
UserProfile::getGlobalProfileData()
{
- http::client()->get_profile(
- userid_.toStdString(),
- [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve profile info for {}",
- userid_.toStdString());
- return;
- }
-
- emit globalUsernameRetrieved(QString::fromStdString(res.display_name));
- globalAvatarUrl = QString::fromStdString(res.avatar_url);
- emit avatarUrlChanged();
- });
+ http::client()->get_profile(
+ userid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve profile info for {}", userid_.toStdString());
+ return;
+ }
+
+ emit globalUsernameRetrieved(QString::fromStdString(res.display_name));
+ globalAvatarUrl = QString::fromStdString(res.avatar_url);
+ emit avatarUrlChanged();
+ });
}
void
UserProfile::openGlobalProfile()
{
- emit manager->openGlobalUserProfile(userid_);
+ emit manager->openGlobalUserProfile(userid_);
}
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index fd8772d5..a148c431 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -18,9 +18,9 @@ Q_NAMESPACE
enum Status
{
- VERIFIED,
- UNVERIFIED,
- BLOCKED
+ VERIFIED,
+ UNVERIFIED,
+ BLOCKED
};
Q_ENUM_NS(Status)
}
@@ -32,128 +32,127 @@ class TimelineViewManager;
class DeviceInfo
{
public:
- DeviceInfo(const QString deviceID,
- const QString displayName,
- verification::Status verification_status_)
- : device_id(deviceID)
- , display_name(displayName)
- , verification_status(verification_status_)
- {}
- DeviceInfo()
- : verification_status(verification::UNVERIFIED)
- {}
-
- QString device_id;
- QString display_name;
-
- verification::Status verification_status;
+ DeviceInfo(const QString deviceID,
+ const QString displayName,
+ verification::Status verification_status_)
+ : device_id(deviceID)
+ , display_name(displayName)
+ , verification_status(verification_status_)
+ {}
+ DeviceInfo()
+ : verification_status(verification::UNVERIFIED)
+ {}
+
+ QString device_id;
+ QString display_name;
+
+ verification::Status verification_status;
};
class DeviceInfoModel : public QAbstractListModel
{
- Q_OBJECT
+ Q_OBJECT
public:
- enum Roles
- {
- DeviceId,
- DeviceName,
- VerificationStatus,
- };
-
- explicit DeviceInfoModel(QObject *parent = nullptr)
- {
- (void)parent;
- connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset);
- };
- QHash<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override
- {
- (void)parent;
- return (int)deviceList_.size();
- }
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ enum Roles
+ {
+ DeviceId,
+ DeviceName,
+ VerificationStatus,
+ };
+
+ explicit DeviceInfoModel(QObject *parent = nullptr)
+ {
+ (void)parent;
+ connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset);
+ };
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override
+ {
+ (void)parent;
+ return (int)deviceList_.size();
+ }
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
- void queueReset(const std::vector<DeviceInfo> &deviceList);
+ void queueReset(const std::vector<DeviceInfo> &deviceList);
public slots:
- void reset(const std::vector<DeviceInfo> &deviceList);
+ void reset(const std::vector<DeviceInfo> &deviceList);
private:
- std::vector<DeviceInfo> deviceList_;
+ std::vector<DeviceInfo> deviceList_;
- friend class UserProfile;
+ friend class UserProfile;
};
class UserProfile : public QObject
{
- Q_OBJECT
- Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
- Q_PROPERTY(QString userid READ userid CONSTANT)
- Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
- Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList NOTIFY devicesChanged)
- Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT)
- Q_PROPERTY(int userVerified READ getUserStatus NOTIFY userStatusChanged)
- Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
- Q_PROPERTY(
- bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged)
- Q_PROPERTY(bool isSelf READ isSelf CONSTANT)
- Q_PROPERTY(TimelineModel *room READ room CONSTANT)
+ Q_OBJECT
+ Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
+ Q_PROPERTY(QString userid READ userid CONSTANT)
+ Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
+ Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList NOTIFY devicesChanged)
+ Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT)
+ Q_PROPERTY(int userVerified READ getUserStatus NOTIFY userStatusChanged)
+ Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
+ Q_PROPERTY(bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged)
+ Q_PROPERTY(bool isSelf READ isSelf CONSTANT)
+ Q_PROPERTY(TimelineModel *room READ room CONSTANT)
public:
- UserProfile(QString roomid,
- QString userid,
- TimelineViewManager *manager_,
- TimelineModel *parent = nullptr);
-
- DeviceInfoModel *deviceList();
-
- QString userid();
- QString displayName();
- QString avatarUrl();
- bool isGlobalUserProfile() const;
- crypto::Trust getUserStatus();
- bool userVerificationEnabled() const;
- bool isSelf() const;
- bool isLoading() const;
- TimelineModel *room() const { return model; }
-
- Q_INVOKABLE void verify(QString device = "");
- Q_INVOKABLE void unverify(QString device = "");
- Q_INVOKABLE void fetchDeviceList(const QString &userID);
- Q_INVOKABLE void banUser();
- // Q_INVOKABLE void ignoreUser();
- Q_INVOKABLE void kickUser();
- Q_INVOKABLE void startChat();
- Q_INVOKABLE void changeUsername(QString username);
- Q_INVOKABLE void changeAvatar();
- Q_INVOKABLE void openGlobalProfile();
+ UserProfile(QString roomid,
+ QString userid,
+ TimelineViewManager *manager_,
+ TimelineModel *parent = nullptr);
+
+ DeviceInfoModel *deviceList();
+
+ QString userid();
+ QString displayName();
+ QString avatarUrl();
+ bool isGlobalUserProfile() const;
+ crypto::Trust getUserStatus();
+ bool userVerificationEnabled() const;
+ bool isSelf() const;
+ bool isLoading() const;
+ TimelineModel *room() const { return model; }
+
+ Q_INVOKABLE void verify(QString device = "");
+ Q_INVOKABLE void unverify(QString device = "");
+ Q_INVOKABLE void fetchDeviceList(const QString &userID);
+ Q_INVOKABLE void banUser();
+ // Q_INVOKABLE void ignoreUser();
+ Q_INVOKABLE void kickUser();
+ Q_INVOKABLE void startChat();
+ Q_INVOKABLE void changeUsername(QString username);
+ Q_INVOKABLE void changeAvatar();
+ Q_INVOKABLE void openGlobalProfile();
signals:
- void userStatusChanged();
- void loadingChanged();
- void displayNameChanged();
- void avatarUrlChanged();
- void displayError(const QString &errorMessage);
- void globalUsernameRetrieved(const QString &globalUser);
- void devicesChanged();
+ void userStatusChanged();
+ void loadingChanged();
+ void displayNameChanged();
+ void avatarUrlChanged();
+ void displayError(const QString &errorMessage);
+ void globalUsernameRetrieved(const QString &globalUser);
+ void devicesChanged();
public slots:
- void updateAvatarUrl();
+ void updateAvatarUrl();
protected slots:
- void setGlobalUsername(const QString &globalUser);
+ void setGlobalUsername(const QString &globalUser);
private:
- void updateRoomMemberState(mtx::events::state::Member member);
- void getGlobalProfileData();
+ void updateRoomMemberState(mtx::events::state::Member member);
+ void getGlobalProfileData();
private:
- QString roomid_, userid_;
- QString globalUsername;
- QString globalAvatarUrl;
- DeviceInfoModel deviceList_;
- crypto::Trust isUserVerified = crypto::Trust::Unverified;
- bool hasMasterKey = false;
- bool isLoading_ = false;
- TimelineViewManager *manager;
- TimelineModel *model;
+ QString roomid_, userid_;
+ QString globalUsername;
+ QString globalAvatarUrl;
+ DeviceInfoModel deviceList_;
+ crypto::Trust isUserVerified = crypto::Trust::Unverified;
+ bool hasMasterKey = false;
+ bool isLoading_ = false;
+ TimelineViewManager *manager;
+ TimelineModel *model;
};
|