diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/AvatarProvider.cpp | 65 | ||||
-rw-r--r-- | src/AvatarProvider.h | 8 | ||||
-rw-r--r-- | src/Cache.cpp | 161 | ||||
-rw-r--r-- | src/Cache.h | 22 | ||||
-rw-r--r-- | src/CacheStructs.h | 1 | ||||
-rw-r--r-- | src/Cache_p.h | 10 | ||||
-rw-r--r-- | src/CommunitiesList.cpp | 36 | ||||
-rw-r--r-- | src/MxcImageProvider.cpp | 18 | ||||
-rw-r--r-- | src/Utils.cpp | 29 | ||||
-rw-r--r-- | src/Utils.h | 3 | ||||
-rw-r--r-- | src/notifications/Manager.cpp | 108 | ||||
-rw-r--r-- | src/notifications/Manager.h | 12 | ||||
-rw-r--r-- | src/notifications/ManagerLinux.cpp | 126 | ||||
-rw-r--r-- | src/notifications/ManagerMac.cpp | 37 | ||||
-rw-r--r-- | src/notifications/ManagerWin.cpp | 23 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 25 |
16 files changed, 207 insertions, 477 deletions
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp index f64f6859..8cc1144f 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp @@ -12,13 +12,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 +34,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 +69,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 473c6319..14b13e43 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 db0f72c9..023d0e57 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -9,10 +9,10 @@ #include <mtxclient/crypto/client.hpp> #include <QByteArray> +#include <QDir> #include <QFileInfo> #include <QStandardPaths> -#include "Cache.h" #include "Logging.h" #include "MatrixClient.h" #include "Utils.h" @@ -60,12 +60,13 @@ MxcImageProvider::download(const QString &id, QString fileName = QString("%1_%2x%3_crop") .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | - QByteArray::OmitTrailingEquals)), - requestedSize.width(), - requestedSize.height()); + QByteArray::OmitTrailingEquals))) + .arg(requestedSize.width()) + .arg(requestedSize.height()); QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", fileName); + QDir().mkpath(fileInfo.absolutePath()); if (fileInfo.exists()) { QImage image(fileInfo.absoluteFilePath()); @@ -102,7 +103,12 @@ MxcImageProvider::download(const QString &id, requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } image.setText("mxc url", "mxc://" + id); - image.save(fileInfo.absoluteFilePath()); + 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()); }); @@ -114,6 +120,7 @@ MxcImageProvider::download(const QString &id, QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", fileName); + QDir().mkpath(fileInfo.absolutePath()); if (fileInfo.exists()) { if (encryptionInfo) { @@ -145,7 +152,6 @@ MxcImageProvider::download(const QString &id, } } } - auto data = cache::image(id); http::client()->download( "mxc://" + id.toStdString(), diff --git a/src/Utils.cpp b/src/Utils.cpp index 4d24c786..8a3b9e4c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -51,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() { diff --git a/src/Utils.h b/src/Utils.h index eb09172e..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); diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 30e74d33..322213dd 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -2,89 +2,35 @@ #include "Cache.h" #include "EventAccessors.h" -#include "Logging.h" -#include "MatrixClient.h" #include "Utils.h" -#include <QFile> -#include <QImage> -#include <QStandardPaths> - -#include <mtxclient/crypto/client.hpp> - QString -NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents &event) +NotificationsManager::getMessageTemplate(const mtx::responses::Notification ¬ification) { - const auto url = mtx::accessors::url(event); - auto encryptionInfo = mtx::accessors::file(event); - - auto filename = QString::fromStdString(mtx::accessors::body(event)); - QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + - filename}; - - bool downloadComplete = false; - - http::client()->download( - url, - [&downloadComplete, &path, 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)); - // the image doesn't exist, so delete the path - path.clear(); - downloadComplete = true; - return; - } - - try { - auto temp = data; - if (encryptionInfo) - temp = mtx::crypto::to_string( - mtx::crypto::decrypt_file(temp, encryptionInfo.value())); - - QFile file{path}; - - if (!file.open(QIODevice::WriteOnly)) { - path.clear(); - downloadComplete = true; - return; - } - - // delete any existing file content - file.resize(0); - - // resize the image - QImage img{utils::readImage(QByteArray{temp.data()})}; - - if (img.isNull()) { - path.clear(); - downloadComplete = true; - return; - } - -#ifdef NHEKO_DBUS_SYS // the images in D-Bus notifications are to be 200x100 max - img.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation) - .save(&file); -#else - img.save(&file); -#endif // NHEKO_DBUS_SYS - - file.close(); - - downloadComplete = true; - return; - } catch (const std::exception &e) { - nhlog::ui()->warn("Error while caching file to: {}", e.what()); - } - }); - - while (!downloadComplete) - continue; - - return path.toHtmlEscaped(); + 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 a1ef9f98..416530e0 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -43,14 +43,15 @@ 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); -private: - QString cacheImage(const mtx::events::collections::TimelineEvents &event); - QString formatNotification(const mtx::responses::Notification ¬ification); - #if defined(NHEKO_DBUS_SYS) public: void closeNotifications(QString roomId); @@ -95,6 +96,9 @@ 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(NHEKO_DBUS_SYS) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 5581252d..2b0e56e2 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -8,6 +8,7 @@ #include <QDebug> #include <QImage> #include <QRegularExpression> +#include <QStringBuilder> #include <QTextDocumentFragment> #include <functional> @@ -17,6 +18,7 @@ #include "Cache.h" #include "EventAccessors.h" +#include "MxcImageProvider.h" #include "Utils.h" NotificationsManager::NotificationsManager(QObject *parent) @@ -59,6 +61,12 @@ NotificationsManager::NotificationsManager(QObject *parent) "NotificationReplied", this, SLOT(notificationReplied(uint, QString))); + + connect(this, + &NotificationsManager::systemPostNotificationCb, + this, + &NotificationsManager::systemPostNotification, + Qt::QueuedConnection); } void @@ -69,9 +77,61 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if 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 text = formatNotification(notification); - systemPostNotification(room_id, event_id, room_name, text, icon); + 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)); } /** @@ -183,68 +243,6 @@ NotificationsManager::notificationClosed(uint id, uint reason) } /** - * @param text This should be an HTML-formatted string. - * - * If D-Bus says that notifications can have body markup, this function will - * automatically format the notification to follow the supported HTML subset - * specified at https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/Markup/ - */ -QString -NotificationsManager::formatNotification(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); - - const auto messageLeadIn = - ((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - ? "* " + sender + " " - : sender + - (utils::isReply(notification.event) - ? tr(" replied", - "Used to denote that this message is a reply to another " - "message. Displayed as 'foo replied: message'.") - : "") + - ": "); - - if (hasMarkup_) { - if (hasImages_ && mtx::accessors::msg_type(notification.event) == - mtx::events::MessageType::Image) { - QString imgPath = cacheImage(notification.event); - if (imgPath.isNull()) - return mtx::accessors::formattedBodyWithFallback(notification.event) - .prepend(messageLeadIn); - else - return QString("<img src=\"file:///" + imgPath + "\" alt=\"" + - mtx::accessors::formattedBodyWithFallback( - notification.event) + - "\">") - .prepend(messageLeadIn); - } - - return mtx::accessors::formattedBodyWithFallback(notification.event) - .prepend(messageLeadIn) - .replace("<em>", "<i>") - .replace("</em>", "</i>") - .replace("<strong>", "<b>") - .replace("</strong>", "</b>") - .replace(QRegularExpression("(<mx-reply>.+\\<\\/mx-reply\\>)"), ""); - } - - return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression("<mx-reply>.+</mx-reply>"), "")) - .toPlainText() - .prepend(messageLeadIn); -} - -/** * Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify * * This function is from the Clementine project (see diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index de5d0875..3a6becad 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -5,6 +5,7 @@ #include "Cache.h" #include "EventAccessors.h" +#include "MxcImageProvider.h" #include "Utils.h" #include <mtx/responses/notifications.hpp> @@ -14,17 +15,7 @@ QString NotificationsManager::formatNotification(const mtx::responses::Notification ¬ification) { - const auto sender = - cache::displayName(QString::fromStdString(notification.room_id), - QString::fromStdString(mtx::accessors::sender(notification.event))); - - return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression("<mx-reply>.+</mx-reply>"), "")) - .toPlainText() - .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - ? "* " + sender + " " - : ""); + return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body; } void @@ -39,25 +30,33 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); - QImage image; - if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) - image = QImage{cacheImage(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, "", image); + objCxxPostNotification(room_name, messageInfo, "", QImage()); } else { const QString messageInfo = (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender); - objCxxPostNotification( - room_name, messageInfo, formatNotification(notification), image); + 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 baafb6dc..d37bff67 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -108,20 +108,11 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); - const auto messageLeadIn = - ((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - ? "* " + sender + " " - : sender + - (utils::isReply(notification.event) - ? tr(" replied", - "Used to denote that this message is a reply to another " - "message. Displayed as 'foo replied: message'.") - : "") + - ": "); - - return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression("<mx-reply>.+</mx-reply>"), "")) - .toPlainText() - .prepend(messageLeadIn); + 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); } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index cfca626a..8e96cb3e 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -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 |