diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp
index 1834e040..b9962cef 100644
--- a/src/AvatarProvider.cpp
+++ b/src/AvatarProvider.cpp
@@ -5,6 +5,7 @@
#include <QBuffer>
#include <QPixmapCache>
+#include <QPointer>
#include <memory>
#include <unordered_map>
@@ -12,13 +13,14 @@
#include "Cache.h"
#include "Logging.h"
#include "MatrixClient.h"
+#include "MxcImageProvider.h"
#include "Utils.h"
static QPixmapCache avatar_cache;
namespace AvatarProvider {
void
-resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback)
+resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback)
{
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
@@ -33,44 +35,32 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
return;
}
- auto data = cache::image(cacheKey);
- if (!data.isNull()) {
- pixmap = QPixmap::fromImage(utils::readImage(&data));
- avatar_cache.insert(cacheKey, pixmap);
- callback(pixmap);
- return;
- }
-
- auto proxy = std::make_shared<AvatarProxy>();
- QObject::connect(proxy.get(),
- &AvatarProxy::avatarDownloaded,
- receiver,
- [callback, cacheKey](QByteArray data) {
- QPixmap pm = QPixmap::fromImage(utils::readImage(&data));
- avatar_cache.insert(cacheKey, pm);
- callback(pm);
- });
+ MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
+ QSize(size, size),
+ [callback, cacheKey, recv = QPointer<QObject>(receiver)](
+ QString, QSize, QImage img, QString) {
+ if (!recv)
+ return;
- mtx::http::ThumbOpts opts;
- opts.width = size;
- opts.height = size;
- opts.mxc_url = avatarUrl.toStdString();
+ 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);
+ });
- http::client()->get_thumbnail(
- opts,
- [opts, cacheKey, proxy = std::move(proxy)](const std::string &res,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to download avatar: {} - ({} {})",
- opts.mxc_url,
- mtx::errors::to_string(err->matrix_error.errcode),
- err->matrix_error.error);
- } else {
- cache::saveImage(cacheKey.toStdString(), res);
- }
+ if (img.isNull()) {
+ emit proxy->avatarDownloaded(QPixmap{});
+ return;
+ }
- emit proxy->avatarDownloaded(QByteArray(res.data(), (int)res.size()));
- });
+ auto pm = QPixmap::fromImage(std::move(img));
+ emit proxy->avatarDownloaded(pm);
+ });
}
void
@@ -80,8 +70,8 @@ resolve(const QString &room_id,
QObject *receiver,
AvatarCallback callback)
{
- const auto avatarUrl = cache::avatarUrl(room_id, user_id);
+ auto avatarUrl = cache::avatarUrl(room_id, user_id);
- resolve(avatarUrl, size, receiver, callback);
+ resolve(std::move(avatarUrl), size, receiver, callback);
}
}
diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h
index 0bea1a8f..173a2fba 100644
--- a/src/AvatarProvider.h
+++ b/src/AvatarProvider.h
@@ -8,19 +8,19 @@
#include <QPixmap>
#include <functional>
+using AvatarCallback = std::function<void(QPixmap)>;
+
class AvatarProxy : public QObject
{
Q_OBJECT
signals:
- void avatarDownloaded(const QByteArray &data);
+ void avatarDownloaded(QPixmap pm);
};
-using AvatarCallback = std::function<void(QPixmap)>;
-
namespace AvatarProvider {
void
-resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback cb);
+resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback cb);
void
resolve(const QString &room_id,
const QString &user_id,
diff --git a/src/Cache.cpp b/src/Cache.cpp
index ec0f2858..4423b21f 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -55,9 +55,6 @@ constexpr auto BATCH_SIZE = 100;
//! Format: room_id -> RoomInfo
constexpr auto ROOMS_DB("rooms");
constexpr auto INVITES_DB("invites");
-//! Keeps already downloaded media for reuse.
-//! Format: matrix_url -> binary data.
-constexpr auto MEDIA_DB("media");
//! Information that must be kept between sync requests.
constexpr auto SYNC_STATE_DB("sync_state");
//! Read receipts per room/event.
@@ -244,7 +241,6 @@ Cache::setup()
syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
- mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
@@ -700,82 +696,6 @@ Cache::secret(const std::string &name)
return secret.toStdString();
}
-//
-// Media Management
-//
-
-void
-Cache::saveImage(const std::string &url, const std::string &img_data)
-{
- if (url.empty() || img_data.empty())
- return;
-
- try {
- auto txn = lmdb::txn::begin(env_);
-
- mediaDb_.put(txn, url, img_data);
-
- txn.commit();
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("saveImage: {}", e.what());
- }
-}
-
-void
-Cache::saveImage(const QString &url, const QByteArray &image)
-{
- saveImage(url.toStdString(), std::string(image.constData(), image.length()));
-}
-
-QByteArray
-Cache::image(lmdb::txn &txn, const std::string &url)
-{
- if (url.empty())
- return QByteArray();
-
- try {
- std::string_view image;
- bool res = mediaDb_.get(txn, url, image);
-
- if (!res)
- return QByteArray();
-
- return QByteArray(image.data(), (int)image.size());
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("image: {}, {}", e.what(), url);
- }
-
- return QByteArray();
-}
-
-QByteArray
-Cache::image(const QString &url)
-{
- if (url.isEmpty())
- return QByteArray();
-
- auto key = url.toStdString();
-
- try {
- auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
-
- std::string_view image;
-
- bool res = mediaDb_.get(txn, key, image);
-
- txn.commit();
-
- if (!res)
- return QByteArray();
-
- return QByteArray(image.data(), (int)image.size());
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("image: {} {}", e.what(), url.toStdString());
- }
-
- return QByteArray();
-}
-
void
Cache::removeInvite(lmdb::txn &txn, const std::string &room_id)
{
@@ -860,7 +780,6 @@ Cache::deleteData()
lmdb::dbi_close(env_, syncStateDb_);
lmdb::dbi_close(env_, roomsDb_);
lmdb::dbi_close(env_, invitesDb_);
- lmdb::dbi_close(env_, mediaDb_);
lmdb::dbi_close(env_, readReceiptsDb_);
lmdb::dbi_close(env_, notificationsDb_);
@@ -2470,50 +2389,6 @@ Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
return QString();
}
-QImage
-Cache::getRoomAvatar(const QString &room_id)
-{
- return getRoomAvatar(room_id.toStdString());
-}
-
-QImage
-Cache::getRoomAvatar(const std::string &room_id)
-{
- auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
-
- std::string_view response;
-
- if (!roomsDb_.get(txn, room_id, response)) {
- txn.commit();
- return QImage();
- }
-
- std::string media_url;
-
- try {
- RoomInfo info = json::parse(response);
- media_url = std::move(info.avatar_url);
-
- if (media_url.empty()) {
- txn.commit();
- return QImage();
- }
- } catch (const json::exception &e) {
- nhlog::db()->warn("failed to parse room info: {}, {}",
- e.what(),
- std::string(response.data(), response.size()));
- }
-
- if (!mediaDb_.get(txn, media_url, response)) {
- txn.commit();
- return QImage();
- }
-
- txn.commit();
-
- return QImage::fromData(QByteArray(response.data(), (int)response.size()));
-}
-
std::vector<std::string>
Cache::joinedRooms()
{
@@ -2615,8 +2490,7 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
MemberInfo tmp = json::parse(user_data);
members.emplace_back(
RoomMember{QString::fromStdString(std::string(user_id)),
- QString::fromStdString(tmp.name),
- QImage::fromData(image(txn, tmp.avatar_url))});
+ QString::fromStdString(tmp.name)});
} catch (const json::exception &e) {
nhlog::db()->warn("{}", e.what());
}
@@ -4240,18 +4114,6 @@ hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id);
}
-//! Retrieves the saved room avatar.
-QImage
-getRoomAvatar(const QString &id)
-{
- return instance_->getRoomAvatar(id);
-}
-QImage
-getRoomAvatar(const std::string &id)
-{
- return instance_->getRoomAvatar(id);
-}
-
void
updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
{
@@ -4276,27 +4138,6 @@ lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id)
return instance_->lastInvisibleEventAfter(room_id, event_id);
}
-QByteArray
-image(const QString &url)
-{
- return instance_->image(url);
-}
-QByteArray
-image(lmdb::txn &txn, const std::string &url)
-{
- return instance_->image(txn, url);
-}
-void
-saveImage(const std::string &url, const std::string &data)
-{
- instance_->saveImage(url, data);
-}
-void
-saveImage(const QString &url, const QByteArray &data)
-{
- instance_->saveImage(url, data);
-}
-
RoomInfo
singleRoomInfo(const std::string &room_id)
{
diff --git a/src/Cache.h b/src/Cache.h
index f7e5f749..e795b32a 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -6,8 +6,6 @@
#pragma once
#include <QDateTime>
-#include <QDir>
-#include <QImage>
#include <QString>
#if __has_include(<lmdbxx/lmdb++.h>)
@@ -135,12 +133,6 @@ hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
const std::string &room_id,
const std::string &user_id);
-//! Retrieves the saved room avatar.
-QImage
-getRoomAvatar(const QString &id);
-QImage
-getRoomAvatar(const std::string &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.
@@ -162,20 +154,6 @@ 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);
-QByteArray
-image(const QString &url);
-QByteArray
-image(lmdb::txn &txn, const std::string &url);
-inline QByteArray
-image(const std::string &url)
-{
- return image(QString::fromStdString(url));
-}
-void
-saveImage(const std::string &url, const std::string &data);
-void
-saveImage(const QString &url, const QByteArray &data);
-
RoomInfo
singleRoomInfo(const std::string &room_id);
std::map<QString, RoomInfo>
diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index ad9aab98..c449f013 100644
--- a/src/CacheStructs.h
+++ b/src/CacheStructs.h
@@ -25,7 +25,6 @@ struct RoomMember
{
QString user_id;
QString display_name;
- QImage avatar;
};
//! Used to uniquely identify a list of read receipts.
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 3454cd54..62927923 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -118,10 +118,6 @@ public:
const std::string &room_id,
const std::string &user_id);
- //! Retrieves the saved room avatar.
- QImage getRoomAvatar(const QString &id);
- QImage getRoomAvatar(const std::string &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.
@@ -137,11 +133,6 @@ public:
using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
UserReceipts readReceipts(const QString &event_id, const QString &room_id);
- QByteArray image(const QString &url);
- QByteArray image(lmdb::txn &txn, const std::string &url);
- void saveImage(const std::string &url, const std::string &data);
- void saveImage(const QString &url, const QByteArray &data);
-
RoomInfo singleRoomInfo(const std::string &room_id);
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
std::vector<std::string> roomsWithTagUpdates(const mtx::responses::Sync &res);
@@ -528,7 +519,6 @@ private:
lmdb::dbi syncStateDb_;
lmdb::dbi roomsDb_;
lmdb::dbi invitesDb_;
- lmdb::dbi mediaDb_;
lmdb::dbi readReceiptsDb_;
lmdb::dbi notificationsDb_;
diff --git a/src/CommunitiesList.cpp b/src/CommunitiesList.cpp
index f644ebee..7cc5d10e 100644
--- a/src/CommunitiesList.cpp
+++ b/src/CommunitiesList.cpp
@@ -6,6 +6,7 @@
#include "Cache.h"
#include "Logging.h"
#include "MatrixClient.h"
+#include "MxcImageProvider.h"
#include "Splitter.h"
#include "UserSettingsPage.h"
@@ -253,37 +254,16 @@ CommunitiesList::highlightSelectedCommunity(const QString &community_id)
void
CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUrl)
{
- auto savedImgData = cache::image(avatarUrl);
- if (!savedImgData.isNull()) {
- QPixmap pix;
- pix.loadFromData(savedImgData);
- emit avatarRetrieved(id, pix);
- return;
- }
-
- if (avatarUrl.isEmpty())
- return;
-
- mtx::http::ThumbOpts opts;
- opts.mxc_url = avatarUrl.toStdString();
- http::client()->get_thumbnail(
- opts, [this, opts, id](const std::string &res, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to download avatar: {} - ({} {})",
- opts.mxc_url,
- mtx::errors::to_string(err->matrix_error.errcode),
- err->matrix_error.error);
+ MxcImageProvider::download(
+ QString(avatarUrl).remove(QStringLiteral("mxc://")),
+ QSize(96, 96),
+ [this, id](QString, QSize, QImage img, QString) {
+ if (img.isNull()) {
+ nhlog::net()->warn("failed to download avatar: {})", id.toStdString());
return;
}
- cache::saveImage(opts.mxc_url, res);
-
- auto data = QByteArray(res.data(), (int)res.size());
-
- QPixmap pix;
- pix.loadFromData(data);
-
- emit avatarRetrieved(id, pix);
+ emit avatarRetrieved(id, QPixmap::fromImage(img));
});
}
diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp
index e4f629a5..a20657c8 100644
--- a/src/MxcImageProvider.cpp
+++ b/src/MxcImageProvider.cpp
@@ -4,106 +4,201 @@
#include "MxcImageProvider.h"
+#include <optional>
+
#include <mtxclient/crypto/client.hpp>
-#include "Cache.h"
+#include <QByteArray>
+#include <QDir>
+#include <QFileInfo>
+#include <QStandardPaths>
+
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
+QHash<QString, mtx::crypto::EncryptedFile> infos;
+
+QQuickImageResponse *
+MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
+{
+ MxcImageResponse *response = new MxcImageResponse(id, requestedSize);
+ pool.start(response);
+ return response;
+}
+
+void
+MxcImageProvider::addEncryptionInfo(mtx::crypto::EncryptedFile info)
+{
+ infos.insert(QString::fromStdString(info.url), info);
+}
void
MxcImageResponse::run()
{
- if (m_requestedSize.isValid() && !m_encryptionInfo) {
- QString fileName = QString("%1_%2x%3_crop")
- .arg(m_id)
- .arg(m_requestedSize.width())
- .arg(m_requestedSize.height());
+ 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();
+ });
+}
+
+void
+MxcImageProvider::download(const QString &id,
+ const QSize &requestedSize,
+ std::function<void(QString, QSize, QImage, QString)> then)
+{
+ std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
+ auto temp = infos.find("mxc://" + id);
+ if (temp != infos.end())
+ encryptionInfo = *temp;
- auto data = cache::image(fileName);
- if (!data.isNull()) {
- m_image = utils::readImage(&data);
+ if (requestedSize.isValid() && !encryptionInfo) {
+ QString fileName =
+ QString("%1_%2x%3_crop")
+ .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
+ QByteArray::OmitTrailingEquals)))
+ .arg(requestedSize.width())
+ .arg(requestedSize.height());
+ QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
+ "/media_cache",
+ fileName);
+ QDir().mkpath(fileInfo.absolutePath());
- if (!m_image.isNull()) {
- m_image = m_image.scaled(
- m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- m_image.setText("mxc url", "mxc://" + m_id);
+ if (fileInfo.exists()) {
+ QImage image(fileInfo.absoluteFilePath());
+ if (!image.isNull()) {
+ image = image.scaled(
+ requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- if (!m_image.isNull()) {
- emit finished();
+ if (!image.isNull()) {
+ then(id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
}
}
mtx::http::ThumbOpts opts;
- opts.mxc_url = "mxc://" + m_id.toStdString();
- opts.width = m_requestedSize.width() > 0 ? m_requestedSize.width() : -1;
- opts.height = m_requestedSize.height() > 0 ? m_requestedSize.height() : -1;
+ 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";
http::client()->get_thumbnail(
- opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) {
+ opts,
+ [fileInfo, requestedSize, then, id](const std::string &res,
+ mtx::http::RequestErr err) {
if (err || res.empty()) {
- nhlog::net()->error("Failed to download image {}",
- m_id.toStdString());
- m_error = "Failed download";
- emit finished();
+ then(id, QSize(), {}, "");
return;
}
- auto data = QByteArray(res.data(), (int)res.size());
- cache::saveImage(fileName, data);
- m_image = utils::readImage(&data);
- if (!m_image.isNull()) {
- m_image = m_image.scaled(
- m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ auto data = QByteArray(res.data(), (int)res.size());
+ QImage image = utils::readImage(data);
+ if (!image.isNull()) {
+ image = image.scaled(
+ requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
- m_image.setText("mxc url", "mxc://" + m_id);
+ 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());
- emit finished();
+ then(id, requestedSize, image, fileInfo.absoluteFilePath());
});
} else {
- auto data = cache::image(m_id);
+ try {
+ QString fileName = QString::fromUtf8(id.toUtf8().toBase64(
+ QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
+ QFileInfo fileInfo(
+ QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
+ "/media_cache",
+ fileName);
+ QDir().mkpath(fileInfo.absolutePath());
- if (!data.isNull()) {
- m_image = utils::readImage(&data);
- m_image.setText("mxc url", "mxc://" + m_id);
+ if (fileInfo.exists()) {
+ if (encryptionInfo) {
+ QFile f(fileInfo.absoluteFilePath());
+ f.open(QIODevice::ReadOnly);
- if (!m_image.isNull()) {
- emit finished();
- return;
+ 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()) {
+ then(id,
+ requestedSize,
+ image,
+ fileInfo.absoluteFilePath());
+ return;
+ }
+ } else {
+ QImage image(fileInfo.absoluteFilePath());
+ if (!image.isNull()) {
+ then(id,
+ requestedSize,
+ image,
+ fileInfo.absoluteFilePath());
+ return;
+ }
+ }
}
- }
- http::client()->download(
- "mxc://" + m_id.toStdString(),
- [this](const std::string &res,
- const std::string &,
- const std::string &originalFilename,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->error("Failed to download image {}",
- m_id.toStdString());
- m_error = "Failed download";
- emit finished();
+ http::client()->download(
+ "mxc://" + id.toStdString(),
+ [fileInfo, requestedSize, then, id, encryptionInfo](
+ const std::string &res,
+ const std::string &,
+ const std::string &originalFilename,
+ mtx::http::RequestErr err) {
+ if (err) {
+ then(id, QSize(), {}, "");
+ return;
+ }
- return;
- }
-
- auto temp = res;
- if (m_encryptionInfo)
- temp = mtx::crypto::to_string(
- mtx::crypto::decrypt_file(temp, m_encryptionInfo.value()));
+ 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();
- auto data = QByteArray(temp.data(), (int)temp.size());
- cache::saveImage(m_id, data);
- m_image = utils::readImage(&data);
- m_image.setText("original filename",
- QString::fromStdString(originalFilename));
- m_image.setText("mxc url", "mxc://" + m_id);
+ 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);
+ image.setText("original filename",
+ QString::fromStdString(originalFilename));
+ image.setText("mxc url", "mxc://" + id);
+ then(
+ id, requestedSize, image, fileInfo.absoluteFilePath());
+ return;
+ }
- emit finished();
- });
+ QImage image(fileInfo.absoluteFilePath());
+ image.setText("original filename",
+ QString::fromStdString(originalFilename));
+ image.setText("mxc url", "mxc://" + id);
+ image.save(fileInfo.absoluteFilePath());
+ 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 f7580bca..7b960836 100644
--- a/src/MxcImageProvider.h
+++ b/src/MxcImageProvider.h
@@ -10,21 +10,18 @@
#include <QImage>
#include <QThreadPool>
-#include <mtx/common.hpp>
+#include <functional>
-#include <boost/optional.hpp>
+#include <mtx/common.hpp>
class MxcImageResponse
: public QQuickImageResponse
, public QRunnable
{
public:
- MxcImageResponse(const QString &id,
- const QSize &requestedSize,
- boost::optional<mtx::crypto::EncryptedFile> encryptionInfo)
+ MxcImageResponse(const QString &id, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
- , m_encryptionInfo(encryptionInfo)
{
setAutoDelete(false);
}
@@ -40,7 +37,6 @@ public:
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
- boost::optional<mtx::crypto::EncryptedFile> m_encryptionInfo;
};
class MxcImageProvider
@@ -50,24 +46,13 @@ class MxcImageProvider
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
- const QSize &requestedSize) override
- {
- boost::optional<mtx::crypto::EncryptedFile> info;
- auto temp = infos.find("mxc://" + id);
- if (temp != infos.end())
- info = *temp;
+ const QSize &requestedSize) override;
- MxcImageResponse *response = new MxcImageResponse(id, requestedSize, info);
- pool.start(response);
- return response;
- }
-
- void addEncryptionInfo(mtx::crypto::EncryptedFile info)
- {
- infos.insert(QString::fromStdString(info.url), info);
- }
+ static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
+ static void download(const QString &id,
+ const QSize &requestedSize,
+ std::function<void(QString, QSize, QImage, QString)> then);
private:
QThreadPool pool;
- QHash<QString, mtx::crypto::EncryptedFile> infos;
};
diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp
index d74f9dc9..ea5de674 100644
--- a/src/RoomInfoListItem.cpp
+++ b/src/RoomInfoListItem.cpp
@@ -8,7 +8,6 @@
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
-#include <QSettings>
#include <QtGlobal>
#include "AvatarProvider.h"
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 5c03c52f..8a3b9e4c 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -24,6 +24,7 @@
#include "Cache.h"
#include "Config.h"
+#include "EventAccessors.h"
#include "MatrixClient.h"
#include "UserSettingsPage.h"
@@ -50,6 +51,35 @@ createDescriptionInfo(const Event &event, const QString &localUser, const QStrin
ts};
}
+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);
+
+ // 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));
+ QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption);
+ while (related.quoted_body.startsWith(">"))
+ related.quoted_body.remove(plainQuote);
+ if (related.quoted_body.startsWith("\n"))
+ related.quoted_body.remove(0, 1);
+ related.quoted_body = utils::getQuoteBody(related);
+ related.quoted_body.replace("@room", QString::fromUtf8("@\u2060room"));
+
+ // get quoted body and strip reply fallback
+ related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event);
+ related.quoted_formatted_body.remove(QRegularExpression(
+ "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
+ related.quoted_formatted_body.replace("@room", "@\u2060aroom");
+ related.room = room_id_;
+
+ return related;
+}
+
QString
utils::localUser()
{
@@ -688,11 +718,17 @@ utils::restoreCombobox(QComboBox *combo, const QString &value)
}
QImage
-utils::readImage(const QByteArray *data)
+utils::readImage(const QByteArray &data)
{
QBuffer buf;
- buf.setData(*data);
+ 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();
+}
diff --git a/src/Utils.h b/src/Utils.h
index 373bed01..f8ead68c 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -40,6 +40,9 @@ namespace utils {
using TimelineEvent = mtx::events::collections::TimelineEvents;
+RelatedInfo
+stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_);
+
bool
codepointIsEmoji(uint code);
@@ -309,5 +312,8 @@ restoreCombobox(QComboBox *combo, const QString &value);
//! Read image respecting exif orientation
QImage
-readImage(const QByteArray *data);
+readImage(const QByteArray &data);
+
+bool
+isReply(const mtx::events::collections::TimelineEvents &e);
}
diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp
new file mode 100644
index 00000000..be580b08
--- /dev/null
+++ b/src/notifications/Manager.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "notifications/Manager.h"
+
+#include "Cache.h"
+#include "EventAccessors.h"
+#include "Utils.h"
+
+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)));
+
+ // 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);
+ }
+}
diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h
index e2b3236a..416530e0 100644
--- a/src/notifications/Manager.h
+++ b/src/notifications/Manager.h
@@ -10,7 +10,12 @@
#include <mtx/responses/notifications.hpp>
+// convenience definition
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU)
+#define NHEKO_DBUS_SYS
+#endif
+
+#if defined(NHEKO_DBUS_SYS)
#include <QtDBus/QDBusArgument>
#include <QtDBus/QDBusInterface>
#endif
@@ -38,20 +43,51 @@ public:
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);
public slots:
void removeNotification(const QString &roomId, const QString &eventId);
-#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU)
+#if defined(NHEKO_DBUS_SYS)
public:
void closeNotifications(QString roomId);
private:
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);
// notification ID to (room ID, event ID)
QMap<uint, roomEventId> notificationIds;
+
+ 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);
+#endif
+
+#if defined(Q_OS_WINDOWS)
+private:
+ void systemPostNotification(const QString &line1,
+ const QString &line2,
+ const QString &iconPath);
#endif
// these slots are platform specific (D-Bus only)
@@ -60,9 +96,12 @@ private slots:
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);
};
-#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU)
+#if defined(NHEKO_DBUS_SYS)
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image);
const QDBusArgument &
diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp
index a222bd36..598b2bd0 100644
--- a/src/notifications/ManagerLinux.cpp
+++ b/src/notifications/ManagerLinux.cpp
@@ -1,3 +1,8 @@
+// SPDX-FileCopyrightText: 2012 Roland Hieber <rohieb@rohieb.name>
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
#include "notifications/Manager.h"
#include <QDBusConnection>
@@ -7,12 +12,19 @@
#include <QDBusPendingReply>
#include <QDebug>
#include <QImage>
+#include <QRegularExpression>
+#include <QStringBuilder>
+#include <QTextDocumentFragment>
+
+#include <functional>
+#include <variant>
+
+#include <mtx/responses/notifications.hpp>
#include "Cache.h"
#include "EventAccessors.h"
-#include "MatrixClient.h"
+#include "MxcImageProvider.h"
#include "Utils.h"
-#include <mtx/responses/notifications.hpp>
NotificationsManager::NotificationsManager(QObject *parent)
: QObject(parent)
@@ -21,6 +33,18 @@ NotificationsManager::NotificationsManager(QObject *parent)
"org.freedesktop.Notifications",
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;
+ })}
+ , hasImages_{std::invoke([this]() -> bool {
+ for (auto x : dbus.call("GetCapabilities").arguments())
+ if (x.toStringList().contains("body-images"))
+ return true;
+ return false;
+ })}
{
qDBusRegisterMetaType<QImage>();
@@ -42,12 +66,13 @@ NotificationsManager::NotificationsManager(QObject *parent)
"NotificationReplied",
this,
SLOT(notificationReplied(uint, QString)));
-}
-// SPDX-FileCopyrightText: 2012 Roland Hieber <rohieb@rohieb.name>
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
+ connect(this,
+ &NotificationsManager::systemPostNotificationCb,
+ this,
+ &NotificationsManager::systemPostNotification,
+ Qt::QueuedConnection);
+}
void
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
@@ -55,25 +80,87 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if
{
const auto room_id = QString::fromStdString(notification.room_id);
const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
- const auto sender = cache::displayName(
- room_id, QString::fromStdString(mtx::accessors::sender(notification.event)));
- const auto text = utils::event_body(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;
+ }
+
+ 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_formatted_body)
+ .replace("<em>", "<i>")
+ .replace("</em>", "</i>")
+ .replace("<strong>", "<b>")
+ .replace("</strong>", "</b>"));
+ return;
+ }
+
+ postNotif(
+ template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
+}
+
+/**
+ * This function is based on code from
+ * https://github.com/rohieb/StratumsphereTrayIcon
+ * Copyright (C) 2012 Roland Hieber <rohieb@rohieb.name>
+ * Licensed under the GNU General Public License, version 3
+ */
+void
+NotificationsManager::systemPostNotification(const QString &room_id,
+ const QString &event_id,
+ const QString &roomName,
+ 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 << QString::fromStdString(
- cache::singleRoomInfo(notification.room_id).name); // summary
-
- // body
- if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote)
- argumentList << "* " + sender + " " + text;
- else
- argumentList << sender + ": " + text;
+ 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.
@@ -84,10 +171,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if
argumentList << hints; // hints
argumentList << (int)-1; // timeout in ms
- static QDBusInterface notifyApp("org.freedesktop.Notifications",
- "/org/freedesktop/Notifications",
- "org.freedesktop.Notifications");
- QDBusPendingCall call = notifyApp.asyncCallWithArgumentList("Notify", argumentList);
+ QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
auto watcher = new QDBusPendingCallWatcher{call, this};
connect(
watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
@@ -103,10 +187,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if
void
NotificationsManager::closeNotification(uint id)
{
- static QDBusInterface closeCall("org.freedesktop.Notifications",
- "/org/freedesktop/Notifications",
- "org.freedesktop.Notifications");
- auto call = closeCall.asyncCall("CloseNotification", (uint)id); // replace_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) {
diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp
new file mode 100644
index 00000000..8e36985c
--- /dev/null
+++ b/src/notifications/ManagerMac.cpp
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Manager.h"
+
+#include <QRegularExpression>
+#include <QTextDocumentFragment>
+
+#include "Cache.h"
+#include "EventAccessors.h"
+#include "MxcImageProvider.h"
+#include "Utils.h"
+
+#include <mtx/responses/notifications.hpp>
+
+#include <variant>
+
+static QString
+formatNotification(const mtx::responses::Notification ¬ification)
+{
+ 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());
+ }
+}
diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm
index 5609d3de..33b7b6af 100644
--- a/src/notifications/ManagerMac.mm
+++ b/src/notifications/ManagerMac.mm
@@ -1,13 +1,10 @@
#include "notifications/Manager.h"
-#include <Foundation/Foundation.h>
-#include <QtMac>
+#import <Foundation/Foundation.h>
+#import <AppKit/NSImage.h>
-#include "Cache.h"
-#include "EventAccessors.h"
-#include "MatrixClient.h"
-#include "Utils.h"
-#include <mtx/responses/notifications.hpp>
+#include <QtMac>
+#include <QImage>
@interface NSUserNotification (CFIPrivate)
- (void)set_identityImage:(NSImage *)image;
@@ -19,24 +16,22 @@ NotificationsManager::NotificationsManager(QObject *parent): QObject(parent)
}
void
-NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
- const QImage &icon)
+NotificationsManager::objCxxPostNotification(const QString &title,
+ const QString &subtitle,
+ const QString &informativeText,
+ const QImage &bodyImage)
{
- Q_UNUSED(icon);
-
- const auto sender = cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event)));
- const auto text = utils::event_body(notification.event);
- NSUserNotification * notif = [[NSUserNotification alloc] init];
+ NSUserNotification *notif = [[NSUserNotification alloc] init];
- notif.title = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name).toNSString();
- notif.subtitle = QString("%1 sent a message").arg(sender).toNSString();
- if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote)
- notif.informativeText = QString("* ").append(sender).append(" ").append(text).toNSString();
- else
- notif.informativeText = text.toNSString();
+ notif.title = title.toNSString();
+ notif.subtitle = subtitle.toNSString();
+ notif.informativeText = informativeText.toNSString();
notif.soundName = NSUserNotificationDefaultSoundName;
+ if (!bodyImage.isNull())
+ notif.contentImage = [[NSImage alloc] initWithCGImage: bodyImage.toCGImage() size: NSZeroSize];
+
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif];
[notif autorelease];
}
@@ -45,7 +40,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if
void
NotificationsManager::actionInvoked(uint, QString)
{
- }
+}
void
NotificationsManager::notificationReplied(uint, QString)
diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp
index 3152d84f..fe7830a7 100644
--- a/src/notifications/ManagerWin.cpp
+++ b/src/notifications/ManagerWin.cpp
@@ -5,11 +5,15 @@
#include "notifications/Manager.h"
#include "wintoastlib.h"
+#include <QRegularExpression>
+#include <QStandardPaths>
+#include <QTextDocumentFragment>
+
+#include <variant>
+
#include "Cache.h"
#include "EventAccessors.h"
-#include "MatrixClient.h"
#include "Utils.h"
-#include <mtx/responses/notifications.hpp>
using namespace WinToastLib;
@@ -45,34 +49,57 @@ 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 text = utils::event_body(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
+NotificationsManager::systemPostNotification(const QString &line1,
+ const QString &line2,
+ const QString &iconPath)
+{
if (!isInitialized)
init();
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
- if (room_name != sender)
- templ.setTextField(QString("%1 - %2").arg(sender).arg(room_name).toStdWString(),
- WinToastTemplate::FirstLine);
- else
- templ.setTextField(QString("%1").arg(sender).toStdWString(),
- WinToastTemplate::FirstLine);
- if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote)
- templ.setTextField(
- QString("* ").append(sender).append(" ").append(text).toStdWString(),
- WinToastTemplate::SecondLine);
- else
- templ.setTextField(QString("%1").arg(text).toStdWString(),
- WinToastTemplate::SecondLine);
- // TODO: implement room or user avatar
- // templ.setImagePath(L"C:/example.png");
+ templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
+ templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
+
+ if (!iconPath.isNull())
+ templ.setImagePath(iconPath.toStdWString());
WinToast::instance()->showToast(templ, new CustomHandler());
}
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 20a3fe10..8a5e4346 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -302,7 +302,9 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown)
// NOTE(Nico): rich replies always need a formatted_body!
text.format = "org.matrix.custom.html";
- if (ChatPage::instance()->userSettings()->markdown())
+ if ((ChatPage::instance()->userSettings()->markdown() &&
+ useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
+ useMarkdown == MarkdownOverride::ON)
text.formatted_body =
utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg))
.toStdString();
@@ -572,7 +574,7 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList &
auto mimeClass = mime.split("/")[0];
nhlog::ui()->debug("Mime: {}", mime.toStdString());
if (mimeClass == "image") {
- QImage img = utils::readImage(&data);
+ QImage img = utils::readImage(data);
dimensions = img.size();
if (img.height() > 200 && img.width() > 360)
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 004cf26a..8e96cb3e 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -369,7 +369,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent();
- bool isReply = relations(event).reply_to().has_value();
+ bool isReply = utils::isReply(event);
auto formattedBody_ = QString::fromStdString(formatted_body(event));
if (formattedBody_.isEmpty()) {
@@ -870,30 +870,7 @@ TimelineModel::relatedInfo(QString id)
if (!event)
return {};
- RelatedInfo related = {};
- related.quoted_user = QString::fromStdString(mtx::accessors::sender(*event));
- related.related_event = id.toStdString();
- 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));
- QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption);
- while (related.quoted_body.startsWith(">"))
- related.quoted_body.remove(plainQuote);
- if (related.quoted_body.startsWith("\n"))
- related.quoted_body.remove(0, 1);
- related.quoted_body = utils::getQuoteBody(related);
- related.quoted_body.replace("@room", QString::fromUtf8("@\u2060room"));
-
- // get quoted body and strip reply fallback
- related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(*event);
- related.quoted_formatted_body.remove(QRegularExpression(
- "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
- related.quoted_formatted_body.replace("@room", "@\u2060aroom");
- related.room = room_id_;
-
- return related;
+ return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_);
}
void
|