From 029ae18a07c8c8692e7ae8836fcdb6ccfbe94e84 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 13 Feb 2021 13:48:37 -0500 Subject: [PATCH 01/39] Format markdown as HTML in notifications --- src/notifications/ManagerLinux.cpp | 6 ++++-- src/notifications/ManagerMac.mm | 6 ++++-- src/notifications/ManagerWin.cpp | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index a222bd36..70f131c2 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -13,6 +13,7 @@ #include "MatrixClient.h" #include "Utils.h" #include +#include NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) @@ -58,6 +59,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const auto sender = cache::displayName( room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); const auto text = utils::event_body(notification.event); + const auto formattedText = cmark_markdown_to_html(text.toStdString().c_str(), text.length(), CMARK_OPT_UNSAFE); QVariantMap hints; hints["image-data"] = icon; @@ -71,9 +73,9 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if // body if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - argumentList << "* " + sender + " " + text; + argumentList << "* " + sender + " " + formattedText; else - argumentList << sender + ": " + text; + argumentList << sender + ": " + formattedText; // 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. diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index 5609d3de..af0b5a02 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -8,6 +8,7 @@ #include "MatrixClient.h" #include "Utils.h" #include +#include @interface NSUserNotification (CFIPrivate) - (void)set_identityImage:(NSImage *)image; @@ -26,15 +27,16 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if 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 formattedText = cmark_markdown_to_html(text.toStdString().c_str(), text.length(), CMARK_OPT_UNSAFE); 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(); + notif.informativeText = QString("* ").append(sender).append(" ").append(formattedText).toNSString(); else - notif.informativeText = text.toNSString(); + notif.informativeText = formattedText.toNSString(); notif.soundName = NSUserNotificationDefaultSoundName; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif]; diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 3152d84f..5a1a867c 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -10,6 +10,7 @@ #include "MatrixClient.h" #include "Utils.h" #include +#include using namespace WinToastLib; @@ -53,6 +54,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); const auto text = utils::event_body(notification.event); + const auto formattedText = cmark_markdown_to_html(text.toStdString().c_str(), text.length(), CMARK_OPT_UNSAFE); if (!isInitialized) init(); @@ -66,10 +68,10 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if WinToastTemplate::FirstLine); if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) templ.setTextField( - QString("* ").append(sender).append(" ").append(text).toStdWString(), + QString("* ").append(sender).append(" ").append(formattedText).toStdWString(), WinToastTemplate::SecondLine); else - templ.setTextField(QString("%1").arg(text).toStdWString(), + templ.setTextField(QString("%1").arg(formattedText).toStdWString(), WinToastTemplate::SecondLine); // TODO: implement room or user avatar // templ.setImagePath(L"C:/example.png"); From c74e68c945d61b0ea1cb25f012949255b3d6c9e7 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 13 Feb 2021 18:18:08 -0500 Subject: [PATCH 02/39] Parse markdown overrides during replies I apparently missed this when I originally added the overrides. --- src/timeline/InputBar.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 20a3fe10..c5168db3 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(); From 3dcbac8875c000127f76318b53f0170456b01f2d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 13 Feb 2021 18:59:17 -0500 Subject: [PATCH 03/39] Only pass formatted text if it is supported (Linux) --- src/notifications/ManagerLinux.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 70f131c2..44dbb196 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "Cache.h" #include "EventAccessors.h" @@ -59,7 +60,14 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const auto sender = cache::displayName( room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); const auto text = utils::event_body(notification.event); - const auto formattedText = cmark_markdown_to_html(text.toStdString().c_str(), text.length(), CMARK_OPT_UNSAFE); + auto formattedText = utils::markdownToHtml(text); + + static QDBusInterface notifyApp("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications"); + auto capabilites = notifyApp.call("GetCapabilites"); + if (!capabilites.arguments().contains("body-markup")) + formattedText = QTextDocumentFragment::fromHtml(formattedText).toPlainText(); QVariantMap hints; hints["image-data"] = icon; @@ -86,9 +94,6 @@ 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); auto watcher = new QDBusPendingCallWatcher{call, this}; connect( From 8d3e463fa60d91ee0876c199bd3d0810e8074821 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 07:57:25 -0500 Subject: [PATCH 04/39] Use plaintext for Windows notifications --- src/notifications/ManagerWin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 5a1a867c..2ddb48cc 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -5,6 +5,8 @@ #include "notifications/Manager.h" #include "wintoastlib.h" +#include + #include "Cache.h" #include "EventAccessors.h" #include "MatrixClient.h" @@ -54,7 +56,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); const auto text = utils::event_body(notification.event); - const auto formattedText = cmark_markdown_to_html(text.toStdString().c_str(), text.length(), CMARK_OPT_UNSAFE); + const auto formattedText = QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); if (!isInitialized) init(); From ae7468a7161b4726c02e6e76d819a27f0e8bb4ea Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 16:52:19 -0500 Subject: [PATCH 05/39] Use the class D-Bus member --- src/notifications/ManagerLinux.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 44dbb196..e81500a0 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -62,10 +62,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const auto text = utils::event_body(notification.event); auto formattedText = utils::markdownToHtml(text); - static QDBusInterface notifyApp("org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications"); - auto capabilites = notifyApp.call("GetCapabilites"); + auto capabilites = dbus.call("GetCapabilites"); if (!capabilites.arguments().contains("body-markup")) formattedText = QTextDocumentFragment::fromHtml(formattedText).toPlainText(); @@ -94,7 +91,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if argumentList << hints; // hints argumentList << (int)-1; // timeout in ms - 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]() { @@ -110,10 +107,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) { From 09303ca49ff7dd48aacb3bb1575a126d11def0f8 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 18:20:47 -0500 Subject: [PATCH 06/39] make lint --- src/timeline/InputBar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index c5168db3..3827c91a 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -303,8 +303,8 @@ 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() && - useMarkdown == MarkdownOverride::NOT_SPECIFIED) || - useMarkdown == MarkdownOverride::ON) + useMarkdown == MarkdownOverride::NOT_SPECIFIED) || + useMarkdown == MarkdownOverride::ON) text.formatted_body = utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg)) .toStdString(); From 648844089c1786f1439ea026d1f3f5d0b757a414 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 18:36:10 -0500 Subject: [PATCH 07/39] Move data parsing into a dedicated function Actually posting the notification is now the responsibility of a private function --- CMakeLists.txt | 5 ++- src/notifications/Manager.cpp | 27 ++++++++++++++++ src/notifications/Manager.h | 10 ++++++ src/notifications/ManagerLinux.cpp | 50 +++++++++++++----------------- src/notifications/ManagerMac.cpp | 11 +++++++ src/notifications/ManagerMac.mm | 30 +++++++----------- src/notifications/ManagerWin.cpp | 44 +++++++++++--------------- 7 files changed, 103 insertions(+), 74 deletions(-) create mode 100644 src/notifications/Manager.cpp create mode 100644 src/notifications/ManagerMac.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 905e6159..e0d9b5a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,6 +297,9 @@ set(SRC_FILES src/ui/UserProfile.cpp src/ui/RoomSettings.cpp + # Generic notification stuff + src/notifications/Manager.cpp + src/AvatarProvider.cpp src/BlurhashProvider.cpp src/Cache.cpp @@ -557,7 +560,7 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC}) if (APPLE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") - set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm) + set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/notifications/ManagerMac.cpp src/emoji/MacHelper.mm) if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON) endif() diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp new file mode 100644 index 00000000..03ce345f --- /dev/null +++ b/src/notifications/Manager.cpp @@ -0,0 +1,27 @@ +#include "notifications/Manager.h" + +#include "Cache.h" +#include "EventAccessors.h" +#include "Utils.h" +#include + +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 sender = cache::displayName( + room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); + + QString text; + if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) + text = + "* " + sender + " " + formatNotification(utils::event_body(notification.event)); + else + text = sender + ":" + formatNotification(utils::event_body(notification.event)); + + systemPostNotification(room_id, event_id, room_name, sender, text, icon); +} diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index e2b3236a..372e4998 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -42,6 +42,16 @@ signals: public slots: void removeNotification(const QString &roomId, const QString &eventId); +private: + void systemPostNotification(const QString &room_id, + const QString &event_id, + const QString &roomName, + const QString &sender, + const QString &text, + const QImage &icon); + + QString formatNotification(const QString &text); + #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU) public: void closeNotifications(QString roomId); diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index e81500a0..35975f53 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -9,12 +9,7 @@ #include #include -#include "Cache.h" -#include "EventAccessors.h" -#include "MatrixClient.h" #include "Utils.h" -#include -#include NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) @@ -52,35 +47,24 @@ NotificationsManager::NotificationsManager(QObject *parent) // SPDX-License-Identifier: GPL-3.0-or-later void -NotificationsManager::postNotification(const mtx::responses::Notification ¬ification, - const QImage &icon) +NotificationsManager::systemPostNotification(const QString &room_id, + const QString &event_id, + const QString &roomName, + const QString &sender, + const QString &text, + 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 sender = cache::displayName( - room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); - const auto text = utils::event_body(notification.event); - auto formattedText = utils::markdownToHtml(text); - - auto capabilites = dbus.call("GetCapabilites"); - if (!capabilites.arguments().contains("body-markup")) - formattedText = QTextDocumentFragment::fromHtml(formattedText).toPlainText(); + Q_UNUSED(sender) QVariantMap hints; hints["image-data"] = icon; hints["sound-name"] = "message-new-instant"; QList 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 + " " + formattedText; - else - argumentList << sender + ": " + formattedText; + 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. @@ -167,6 +151,16 @@ NotificationsManager::notificationClosed(uint id, uint reason) notificationIds.remove(id); } +QString +NotificationsManager::formatNotification(const QString &text) +{ + static auto capabilites = dbus.call("GetCapabilites"); + if (capabilites.arguments().contains("body-markup")) + return utils::markdownToHtml(text); + else + return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); +} + /** * Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify * diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp new file mode 100644 index 00000000..41aea0d1 --- /dev/null +++ b/src/notifications/ManagerMac.cpp @@ -0,0 +1,11 @@ +#include "Manager.h" + +#include + +#include "Utils.h" + +QString +NotificationsManager::formatNotification(const QString &text) +{ + return utils::markdownToHtml(text); +} diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index af0b5a02..3372c5af 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -3,13 +3,6 @@ #include #include -#include "Cache.h" -#include "EventAccessors.h" -#include "MatrixClient.h" -#include "Utils.h" -#include -#include - @interface NSUserNotification (CFIPrivate) - (void)set_identityImage:(NSImage *)image; @end @@ -20,23 +13,22 @@ NotificationsManager::NotificationsManager(QObject *parent): QObject(parent) } void -NotificationsManager::postNotification(const mtx::responses::Notification ¬ification, - const QImage &icon) +NotificationsManager::systemPostNotification(const QString &room_id, + const QString &event_id, + const QString &roomName, + const QString &sender, + const QString &text, + const QImage &icon) { - 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); - const auto formattedText = cmark_markdown_to_html(text.toStdString().c_str(), text.length(), CMARK_OPT_UNSAFE); + Q_UNUSED(room_id) + Q_UNUSED(event_id) + Q_UNUSED(icon) NSUserNotification * notif = [[NSUserNotification alloc] init]; - notif.title = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name).toNSString(); + notif.title = roomName.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(formattedText).toNSString(); - else - notif.informativeText = formattedText.toNSString(); + notif.informativeText = text.toNSString(); notif.soundName = NSUserNotificationDefaultSoundName; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif]; diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 2ddb48cc..42289b84 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -7,12 +7,7 @@ #include -#include "Cache.h" -#include "EventAccessors.h" -#include "MatrixClient.h" #include "Utils.h" -#include -#include using namespace WinToastLib; @@ -45,36 +40,27 @@ NotificationsManager::NotificationsManager(QObject *parent) {} void -NotificationsManager::postNotification(const mtx::responses::Notification ¬ification, - const QImage &icon) +NotificationsManager::systemPostNotification(const QString &room_id, + const QString &event_id, + const QString &roomName, + const QString &sender, + const QString &text, + const QImage &icon) { + Q_UNUSED(room_id) + Q_UNUSED(event_id) 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 formattedText = QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); - if (!isInitialized) init(); auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02); - if (room_name != sender) - templ.setTextField(QString("%1 - %2").arg(sender).arg(room_name).toStdWString(), + if (roomName != sender) + templ.setTextField(QString("%1 - %2").arg(sender).arg(roomName).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(formattedText).toStdWString(), - WinToastTemplate::SecondLine); - else - templ.setTextField(QString("%1").arg(formattedText).toStdWString(), - WinToastTemplate::SecondLine); + templ.setTextField(sender.toStdWString(), WinToastTemplate::FirstLine); + templ.setTextField(text.toStdWString(), WinToastTemplate::SecondLine); // TODO: implement room or user avatar // templ.setImagePath(L"C:/example.png"); @@ -89,3 +75,9 @@ void NotificationsManager::notificationClosed(uint, uint) {} void NotificationsManager::removeNotification(const QString &, const QString &) {} + +QString +NotificationsManager::formatNotification(const QString &text) +{ + return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); +} From e630504863f30ab7958a69a4c00bbc51a403fef4 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 19:23:14 -0500 Subject: [PATCH 08/39] Disable HTML on macOS --- src/notifications/ManagerMac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 41aea0d1..04044d86 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -7,5 +7,5 @@ QString NotificationsManager::formatNotification(const QString &text) { - return utils::markdownToHtml(text); + return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); } From 01bbec88dd8c409ec339e7adf4fe2657c05299b6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 19:30:04 -0500 Subject: [PATCH 09/39] Don't run markdownToHtml on messages --- src/notifications/ManagerLinux.cpp | 4 ++-- src/notifications/ManagerMac.cpp | 2 +- src/notifications/ManagerWin.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 35975f53..df4722be 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -156,9 +156,9 @@ NotificationsManager::formatNotification(const QString &text) { static auto capabilites = dbus.call("GetCapabilites"); if (capabilites.arguments().contains("body-markup")) - return utils::markdownToHtml(text); + return text; else - return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); + return QTextDocumentFragment::fromHtml(text).toPlainText(); } /** diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 04044d86..c75d2283 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -7,5 +7,5 @@ QString NotificationsManager::formatNotification(const QString &text) { - return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); + return QTextDocumentFragment::fromHtml(text).toPlainText(); } diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 42289b84..3ab85a5f 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -79,5 +79,5 @@ NotificationsManager::removeNotification(const QString &, const QString &) QString NotificationsManager::formatNotification(const QString &text) { - return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); + return QTextDocumentFragment::fromHtml(text).toPlainText(); } From b05657d51af7fa18b96eb664b353d620f5b6f104 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 19 Feb 2021 19:31:48 -0500 Subject: [PATCH 10/39] Fix colon spacing --- src/notifications/Manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 03ce345f..083107fb 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -21,7 +21,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if text = "* " + sender + " " + formatNotification(utils::event_body(notification.event)); else - text = sender + ":" + formatNotification(utils::event_body(notification.event)); + text = sender + ": " + formatNotification(utils::event_body(notification.event)); systemPostNotification(room_id, event_id, room_name, sender, text, icon); } From dcd9b80dde6472342a8d3e653345677391840869 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 19 Feb 2021 20:13:27 -0500 Subject: [PATCH 11/39] Fix Linux HTML notifications --- src/notifications/ManagerLinux.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index df4722be..c6326254 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -154,11 +154,12 @@ NotificationsManager::notificationClosed(uint id, uint reason) QString NotificationsManager::formatNotification(const QString &text) { - static auto capabilites = dbus.call("GetCapabilites"); - if (capabilites.arguments().contains("body-markup")) - return text; - else - return QTextDocumentFragment::fromHtml(text).toPlainText(); + static auto capabilites = dbus.call("GetCapabilities").arguments(); + for (auto x : capabilites) + if (x.toStringList().contains("body-markup")) + return utils::markdownToHtml(text); + + return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); } /** From c38c6fe49e9c732f6e784cda04414d82bc092c48 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 11:52:05 -0500 Subject: [PATCH 12/39] Format notifications according to the FreeDesktop specification --- src/notifications/ManagerLinux.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index c6326254..adaa0a35 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -151,15 +151,26 @@ NotificationsManager::notificationClosed(uint id, uint reason) notificationIds.remove(id); } +/** + * @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 QString &text) { static auto capabilites = dbus.call("GetCapabilities").arguments(); for (auto x : capabilites) if (x.toStringList().contains("body-markup")) - return utils::markdownToHtml(text); + return QString(text) + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", ""); - return QTextDocumentFragment::fromHtml(utils::markdownToHtml(text)).toPlainText(); + return QTextDocumentFragment::fromHtml(text).toPlainText(); } /** From 4150d75be7c8945808434d67bd23bb688707af29 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 11:57:42 -0500 Subject: [PATCH 13/39] Only HTML-format the body if it should be formatted --- src/notifications/Manager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 083107fb..dda06299 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -19,9 +19,11 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if QString text; if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) text = - "* " + sender + " " + formatNotification(utils::event_body(notification.event)); + formatNotification("* " + sender + " " + + mtx::accessors::formattedBodyWithFallback(notification.event)); else - text = sender + ": " + formatNotification(utils::event_body(notification.event)); + text = formatNotification( + sender + ": " + mtx::accessors::formattedBodyWithFallback(notification.event)); systemPostNotification(room_id, event_id, room_name, sender, text, icon); } From 4a86e14d04d2f20d02f41801e755e1b2be803e48 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 12:00:13 -0500 Subject: [PATCH 14/39] Simplify determination of whether markup is supported This should also result in a speed increase (however slight), since the capabilities are now sorted through only once. --- src/notifications/ManagerLinux.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index adaa0a35..ab4a6d93 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "Utils.h" NotificationsManager::NotificationsManager(QObject *parent) @@ -161,14 +163,18 @@ NotificationsManager::notificationClosed(uint id, uint reason) QString NotificationsManager::formatNotification(const QString &text) { - static auto capabilites = dbus.call("GetCapabilities").arguments(); - for (auto x : capabilites) - if (x.toStringList().contains("body-markup")) - return QString(text) - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", ""); + static const auto hasMarkup = std::invoke([this]() -> bool { + for (auto x : dbus.call("GetCapabilities").arguments()) + if (x.toStringList().contains("body-markup")) + return true; + return false; + }); + if (hasMarkup) + return QString(text) + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", ""); return QTextDocumentFragment::fromHtml(text).toPlainText(); } From d8fb4d92929eefa1a0d982bac2bcfd7db21fcb7e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 12:06:43 -0500 Subject: [PATCH 15/39] Simplify message body construction --- src/notifications/Manager.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index dda06299..6550445d 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -16,14 +16,11 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const auto sender = cache::displayName( room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); - QString text; - if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - text = - formatNotification("* " + sender + " " + - mtx::accessors::formattedBodyWithFallback(notification.event)); - else - text = formatNotification( - sender + ": " + mtx::accessors::formattedBodyWithFallback(notification.event)); + QString text = + ((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) + ? "* " + sender + " " + : sender + ": ") + + formatNotification(mtx::accessors::formattedBodyWithFallback(notification.event)); systemPostNotification(room_id, event_id, room_name, sender, text, icon); } From 39576fea96c6cac275addc1602f1cc5af464f745 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:05:21 -0500 Subject: [PATCH 16/39] Create function for processing whether a message is a reply --- src/Utils.cpp | 7 +++++++ src/Utils.h | 3 +++ src/timeline/TimelineModel.cpp | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 5c03c52f..40c16fd3 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" @@ -696,3 +697,9 @@ utils::readImage(const QByteArray *data) 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..1dc5ffd6 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -310,4 +310,7 @@ restoreCombobox(QComboBox *combo, const QString &value); //! Read image respecting exif orientation QImage readImage(const QByteArray *data); + +bool +isReply(const mtx::events::collections::TimelineEvents &e); } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 004cf26a..cfca626a 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()) { From b57b76d948407e36f05de51d80289dc82bf63464 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:10:14 -0500 Subject: [PATCH 17/39] Add "replied" marker to regular reply messages --- src/notifications/Manager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 6550445d..eee695a5 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -16,10 +16,17 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const auto sender = cache::displayName( room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); + const QString reply = (utils::isReply(notification.event) + ? "" + : tr(" replied", + "Used to denote that this message is a reply to another " + "message. Displayed as 'foo replied: message'.")); + + // the "replied" is only added if this message is not an emote message QString text = ((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) ? "* " + sender + " " - : sender + ": ") + + : sender + reply + ": ") + formatNotification(mtx::accessors::formattedBodyWithFallback(notification.event)); systemPostNotification(room_id, event_id, room_name, sender, text, icon); From df998ef67194bdd633df163eef53ec628fc8e090 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:16:43 -0500 Subject: [PATCH 18/39] Get event text in event parser function --- src/notifications/Manager.cpp | 2 +- src/notifications/Manager.h | 2 +- src/notifications/ManagerLinux.cpp | 9 ++++++--- src/notifications/ManagerMac.cpp | 5 +++-- src/notifications/ManagerWin.cpp | 6 ++++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index eee695a5..e1f59cb1 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -27,7 +27,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if ((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) ? "* " + sender + " " : sender + reply + ": ") + - formatNotification(mtx::accessors::formattedBodyWithFallback(notification.event)); + formatNotification(notification.event); systemPostNotification(room_id, event_id, room_name, sender, text, icon); } diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index 372e4998..449a609f 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -50,7 +50,7 @@ private: const QString &text, const QImage &icon); - QString formatNotification(const QString &text); + QString formatNotification(const mtx::events::collections::TimelineEvents &e); #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU) public: diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index ab4a6d93..202f2a9d 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -11,6 +11,7 @@ #include +#include "EventAccessors.h" #include "Utils.h" NotificationsManager::NotificationsManager(QObject *parent) @@ -161,7 +162,7 @@ NotificationsManager::notificationClosed(uint id, uint reason) * specified at https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/Markup/ */ QString -NotificationsManager::formatNotification(const QString &text) +NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) { static const auto hasMarkup = std::invoke([this]() -> bool { for (auto x : dbus.call("GetCapabilities").arguments()) @@ -169,14 +170,16 @@ NotificationsManager::formatNotification(const QString &text) return true; return false; }); + if (hasMarkup) - return QString(text) + return mtx::accessors::formattedBodyWithFallback(e) .replace("", "") .replace("", "") .replace("", "") .replace("", ""); - return QTextDocumentFragment::fromHtml(text).toPlainText(); + return QTextDocumentFragment::fromHtml(mtx::accessors::formattedBodyWithFallback(e)) + .toPlainText(); } /** diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index c75d2283..a74df2c7 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -2,10 +2,11 @@ #include +#include "EventAccessors.h" #include "Utils.h" QString -NotificationsManager::formatNotification(const QString &text) +NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) { - return QTextDocumentFragment::fromHtml(text).toPlainText(); + return QTextDocumentFragment::fromHtml(mtx::accessors::formattedBodyWithFallback(e)).toPlainText(); } diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 3ab85a5f..47b4c178 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -7,6 +7,7 @@ #include +#include "EventAccessors.h" #include "Utils.h" using namespace WinToastLib; @@ -77,7 +78,8 @@ NotificationsManager::removeNotification(const QString &, const QString &) {} QString -NotificationsManager::formatNotification(const QString &text) +NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) { - return QTextDocumentFragment::fromHtml(text).toPlainText(); + return QTextDocumentFragment::fromHtml(mtx::accessors::formattedBodyWithFallback(e)).toPlainText(); } + From c693d545985a3e142bb3d8f30cc6f47988488142 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:58:04 -0500 Subject: [PATCH 19/39] Fix when "replied" is displayed I accidentally put it in backwards. --- src/notifications/Manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index e1f59cb1..1e4d8167 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -17,10 +17,10 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); const QString reply = (utils::isReply(notification.event) - ? "" - : tr(" replied", + ? tr(" replied", "Used to denote that this message is a reply to another " - "message. Displayed as 'foo replied: message'.")); + "message. Displayed as 'foo replied: message'.") + : ""); // the "replied" is only added if this message is not an emote message QString text = From 37acdad92822c3d56026bfc425a4576152d89a4e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 14:00:13 -0500 Subject: [PATCH 20/39] Add regex to remove replies in notifications --- src/notifications/ManagerLinux.cpp | 8 ++++++-- src/notifications/ManagerMac.cpp | 6 +++++- src/notifications/ManagerWin.cpp | 7 +++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 202f2a9d..9bcda1b2 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -176,9 +177,12 @@ NotificationsManager::formatNotification(const mtx::events::collections::Timelin .replace("", "") .replace("", "") .replace("", "") - .replace("", ""); + .replace("", "") + .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), ""); - return QTextDocumentFragment::fromHtml(mtx::accessors::formattedBodyWithFallback(e)) + return QTextDocumentFragment::fromHtml( + mtx::accessors::formattedBodyWithFallback(e).replace( + QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) .toPlainText(); } diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index a74df2c7..c9678638 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -1,5 +1,6 @@ #include "Manager.h" +#include #include #include "EventAccessors.h" @@ -8,5 +9,8 @@ QString NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) { - return QTextDocumentFragment::fromHtml(mtx::accessors::formattedBodyWithFallback(e)).toPlainText(); + return QTextDocumentFragment::fromHtml( + mtx::accessors::formattedBodyWithFallback(e).replace( + QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .toPlainText(); } diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 47b4c178..026c912f 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -5,6 +5,7 @@ #include "notifications/Manager.h" #include "wintoastlib.h" +#include #include #include "EventAccessors.h" @@ -80,6 +81,8 @@ NotificationsManager::removeNotification(const QString &, const QString &) QString NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) { - return QTextDocumentFragment::fromHtml(mtx::accessors::formattedBodyWithFallback(e)).toPlainText(); + return QTextDocumentFragment::fromHtml( + mtx::accessors::formattedBodyWithFallback(e).replace( + QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .toPlainText(); } - From f578272a0d645bcfae5d70f6e4aa1dc4649511f1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:17:57 +0100 Subject: [PATCH 21/39] Rewrite notification posting logic This does away with the nice abstraction layers in order to easily get the best-looking notifications for each platform. --- src/notifications/Manager.cpp | 88 ++++++++++++++++++++++-------- src/notifications/Manager.h | 46 ++++++++++++---- src/notifications/ManagerLinux.cpp | 83 +++++++++++++++++++++------- src/notifications/ManagerMac.cpp | 50 +++++++++++++++-- src/notifications/ManagerMac.mm | 31 ++++++----- src/notifications/ManagerWin.cpp | 46 +++++++++++----- 6 files changed, 261 insertions(+), 83 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 1e4d8167..6fa06cb2 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -2,32 +2,76 @@ #include "Cache.h" #include "EventAccessors.h" +#include "Logging.h" +#include "MatrixClient.h" #include "Utils.h" -#include -void -NotificationsManager::postNotification(const mtx::responses::Notification ¬ification, - const QImage &icon) +#include +#include +#include + +#include + +QString +NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents &event) { - 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 sender = cache::displayName( - room_id, QString::fromStdString(mtx::accessors::sender(notification.event))); + const auto url = mtx::accessors::url(event); + auto encryptionInfo = mtx::accessors::file(event); - const QString reply = (utils::isReply(notification.event) - ? tr(" replied", - "Used to denote that this message is a reply to another " - "message. Displayed as 'foo replied: message'.") - : ""); + auto filename = QString::fromStdString(mtx::accessors::body(event)); + QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + + filename}; - // the "replied" is only added if this message is not an emote message - QString text = - ((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - ? "* " + sender + " " - : sender + reply + ": ") + - formatNotification(notification.event); + http::client()->download( + url, + [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(err->status_code)); + return; + } - systemPostNotification(room_id, event_id, room_name, sender, text, icon); + 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)) + return; + + // delete any existing file content + file.resize(0); + file.write(QByteArray(temp.data(), (int)temp.size())); + + // resize the image (really inefficient, I know, but I can't find any + // better way right off + QImage img{path}; + + // delete existing contents + file.resize(0); + + // make sure to save as PNG (because Plasma doesn't do JPEG in + // notifications) + // if (!file.fileName().endsWith(".png")) + // file.rename(file.fileName() + ".png"); + + img.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation) + .save(&file); + file.close(); + + return; + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while caching file to: {}", e.what()); + } + }); + + return path.toHtmlEscaped(); } diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index 449a609f..ef049914 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -10,7 +10,12 @@ #include +// 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 #include #endif @@ -43,25 +48,46 @@ public slots: void removeNotification(const QString &roomId, const QString &eventId); private: - void systemPostNotification(const QString &room_id, - const QString &event_id, - const QString &roomName, - const QString &sender, - const QString &text, - const QImage &icon); + QString cacheImage(const mtx::events::collections::TimelineEvents &event); + QString formatNotification(const mtx::responses::Notification ¬ification); - QString formatNotification(const mtx::events::collections::TimelineEvents &e); - -#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 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 &roomName, + const QString &sender, + const QString &text, + const QImage &icon); #endif // these slots are platform specific (D-Bus only) @@ -72,7 +98,7 @@ private slots: void notificationReplied(uint id, QString reply); }; -#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 9bcda1b2..ae6fdbee 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -12,6 +12,9 @@ #include +#include + +#include "Cache.h" #include "EventAccessors.h" #include "Utils.h" @@ -22,6 +25,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(); @@ -45,21 +60,32 @@ NotificationsManager::NotificationsManager(QObject *parent) SLOT(notificationReplied(uint, QString))); } -// SPDX-FileCopyrightText: 2012 Roland Hieber -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later +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 text = formatNotification(notification); + systemPostNotification(room_id, event_id, room_name, text, icon); +} + +/** + * This function is based on code from + * https://github.com/rohieb/StratumsphereTrayIcon + * Copyright (C) 2012 Roland Hieber + * 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 &sender, const QString &text, const QImage &icon) { - Q_UNUSED(sender) - QVariantMap hints; hints["image-data"] = icon; hints["sound-name"] = "message-new-instant"; @@ -163,27 +189,46 @@ NotificationsManager::notificationClosed(uint id, uint reason) * specified at https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/Markup/ */ QString -NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) +NotificationsManager::formatNotification(const mtx::responses::Notification ¬ification) { - static const auto hasMarkup = std::invoke([this]() -> bool { - for (auto x : dbus.call("GetCapabilities").arguments()) - if (x.toStringList().contains("body-markup")) - return true; - return false; - }); + const auto sender = + 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'.") + : "") + + ": "); - if (hasMarkup) - return mtx::accessors::formattedBodyWithFallback(e) + if (hasMarkup_) { + if (hasImages_ && + mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) + return QString( + "\""") + .prepend(messageLeadIn); + + return mtx::accessors::formattedBodyWithFallback(notification.event) + .prepend(messageLeadIn) .replace("", "") .replace("", "") .replace("", "") .replace("", "") .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), ""); + } return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(e).replace( - QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) - .toPlainText(); + mtx::accessors::formattedBodyWithFallback(notification.event) + .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .toPlainText() + .prepend(messageLeadIn); } /** diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index c9678638..12d8ab6f 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -3,14 +3,56 @@ #include #include +#include "Cache.h" #include "EventAccessors.h" #include "Utils.h" +#include + QString -NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) +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(e).replace( - QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) - .toPlainText(); + mtx::accessors::formattedBodyWithFallback(notification.event) + .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .toPlainText() + .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) + ? "* " + sender + " " + : ""); +} + +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 QString messageInfo = + QString("%1 %2 a message") + .arg(sender) + .arg((utils::isReply(notification.event) + ? tr("replied to", + "Used to denote that this message is a reply to another " + "message. Displayed as 'foo replied to a message'.") + : tr("sent", + "Used to denote that this message is a normal message. Displayed as 'foo " + "sent a message'."))); + + QString text = formatNotification(notification); + + QImage *image = nullptr; + if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) + image = new QImage{cacheImage(notification.event)}; + + objCxxPostNotification(room_name, messageInfo, text, image); } diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index 3372c5af..226bcce0 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -1,7 +1,10 @@ #include "notifications/Manager.h" -#include +#import +#import + #include +#include @interface NSUserNotification (CFIPrivate) - (void)set_identityImage:(NSImage *)image; @@ -13,24 +16,22 @@ NotificationsManager::NotificationsManager(QObject *parent): QObject(parent) } void -NotificationsManager::systemPostNotification(const QString &room_id, - const QString &event_id, - const QString &roomName, - const QString &sender, - const QString &text, - const QImage &icon) +NotificationsManager::objCxxPostNotification(const QString &title, + const QString &subtitle, + const QString &informativeText, + const QImage *bodyImage) { - Q_UNUSED(room_id) - Q_UNUSED(event_id) - Q_UNUSED(icon) - NSUserNotification * notif = [[NSUserNotification alloc] init]; + NSUserNotification *notif = [[NSUserNotification alloc] init]; - notif.title = roomName.toNSString(); - notif.subtitle = QString("%1 sent a message").arg(sender).toNSString(); - notif.informativeText = text.toNSString(); + notif.title = title.toNSString(); + notif.subtitle = subtitle.toNSString(); + notif.informativeText = informativeText.toNSString(); notif.soundName = NSUserNotificationDefaultSoundName; + if (bodyImage != nullptr) + notif.contentImage = [[NSImage alloc] initWithCGImage: bodyImage->toCGImage() size: NSZeroSize]; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif]; [notif autorelease]; } @@ -39,7 +40,7 @@ NotificationsManager::systemPostNotification(const QString &room_id, void NotificationsManager::actionInvoked(uint, QString) { - } +} void NotificationsManager::notificationReplied(uint, QString) diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 026c912f..b17c6e3b 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -6,8 +6,10 @@ #include "wintoastlib.h" #include +#include #include +#include "Cache.h" #include "EventAccessors.h" #include "Utils.h" @@ -42,17 +44,25 @@ NotificationsManager::NotificationsManager(QObject *parent) {} void -NotificationsManager::systemPostNotification(const QString &room_id, - const QString &event_id, - const QString &roomName, +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 text = formatNotification(notification); + + systemPostNotification(room_name, sender, text, icon); +} + +void +NotificationsManager::systemPostNotification(const QString &roomName, const QString &sender, const QString &text, const QImage &icon) { - Q_UNUSED(room_id) - Q_UNUSED(event_id) - Q_UNUSED(icon) - if (!isInitialized) init(); @@ -63,8 +73,11 @@ NotificationsManager::systemPostNotification(const QString &room_id, else templ.setTextField(sender.toStdWString(), WinToastTemplate::FirstLine); templ.setTextField(text.toStdWString(), WinToastTemplate::SecondLine); - // TODO: implement room or user avatar - // templ.setImagePath(L"C:/example.png"); + + auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + roomName + + "-room-avatar.png"; + if (icon.save(iconPath)) + templ.setImagePath(iconPath.toStdWString()); WinToast::instance()->showToast(templ, new CustomHandler()); } @@ -79,10 +92,17 @@ NotificationsManager::removeNotification(const QString &, const QString &) {} QString -NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) +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(e).replace( - QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) - .toPlainText(); + mtx::accessors::formattedBodyWithFallback(notification.event) + .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .toPlainText() + .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) + ? "* " + sender + " " + : ""); } From 9168c2c785fcb0758d78fb95c7f03b6d30468f7e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 26 Feb 2021 13:42:17 -0500 Subject: [PATCH 22/39] Remove unnecessary header --- src/RoomInfoListItem.cpp | 1 - 1 file changed, 1 deletion(-) 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 #include #include -#include #include #include "AvatarProvider.h" From 2192e8bea85e78435830fcb9c8a98c50637c86f8 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 26 Feb 2021 15:33:27 -0500 Subject: [PATCH 23/39] Better handle encrypted notifications --- src/notifications/ManagerLinux.cpp | 8 ++++++++ src/notifications/ManagerMac.cpp | 33 +++++++++++++++++------------- src/notifications/ManagerWin.cpp | 33 ++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index ae6fdbee..56fc8221 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -194,6 +195,13 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ 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>( + ¬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 + " " diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 12d8ab6f..973afdc4 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -9,6 +9,8 @@ #include +#include + QString NotificationsManager::formatNotification(const mtx::responses::Notification ¬ification) { @@ -37,22 +39,25 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); - const QString messageInfo = - QString("%1 %2 a message") - .arg(sender) - .arg((utils::isReply(notification.event) - ? tr("replied to", - "Used to denote that this message is a reply to another " - "message. Displayed as 'foo replied to a message'.") - : tr("sent", - "Used to denote that this message is a normal message. Displayed as 'foo " - "sent a message'."))); - - QString text = formatNotification(notification); - QImage *image = nullptr; if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) image = new QImage{cacheImage(notification.event)}; - objCxxPostNotification(room_name, messageInfo, text, image); + const auto isEncrypted = + std::get_if>( + ¬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); + } 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); + } } diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index b17c6e3b..5b134c0b 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "Cache.h" #include "EventAccessors.h" #include "Utils.h" @@ -52,7 +54,21 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const auto sender = cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); - const auto text = formatNotification(notification); + + const auto isEncrypted = + std::get_if>( + ¬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 text = (isReply ? tr("%1 replied with an encrypted message") + : tr("%1 sent an encrypted message")) + .arg(sender); + systemPostNotification(room_name, sender, text, icon); + } else { + systemPostNotification(room_name, sender, formatNotification(notification), icon); + } systemPostNotification(room_name, sender, text, icon); } @@ -98,11 +114,20 @@ 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\\>)"), "")) .toPlainText() - .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) - ? "* " + sender + " " - : ""); + .prepend(messageLeadIn); } From 3748d7853e196d0d34571caf8cd1957ea001e68f Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 26 Feb 2021 15:38:15 -0500 Subject: [PATCH 24/39] Simplify formatting on Windows --- src/notifications/Manager.h | 7 +++--- src/notifications/ManagerWin.cpp | 40 ++++++++++++++------------------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index ef049914..d7820b18 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -84,10 +84,9 @@ private: #if defined(Q_OS_WINDOWS) private: - void systemPostNotification(const QString &roomName, - const QString &sender, - const QString &text, - const QImage &icon); + void systemPostNotification(const QString &line1, + const QString &line2, + const QString &iconPath); #endif // these slots are platform specific (D-Bus only) diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 5b134c0b..9c45d166 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -60,39 +60,33 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if ¬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 text = (isReply ? tr("%1 replied with an encrypted message") - : tr("%1 sent an encrypted message")) - .arg(sender); - systemPostNotification(room_name, sender, text, icon); - } else { - systemPostNotification(room_name, sender, formatNotification(notification), icon); - } + 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(notification)); - systemPostNotification(room_name, sender, text, icon); + 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 &roomName, - const QString &sender, - const QString &text, - const QImage &icon) +NotificationsManager::systemPostNotification(const QString &line1, + const QString &line2, + const QString &iconPath) { if (!isInitialized) init(); auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02); - if (roomName != sender) - templ.setTextField(QString("%1 - %2").arg(sender).arg(roomName).toStdWString(), - WinToastTemplate::FirstLine); - else - templ.setTextField(sender.toStdWString(), WinToastTemplate::FirstLine); - templ.setTextField(text.toStdWString(), WinToastTemplate::SecondLine); + templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine); + templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine); - auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + roomName + - "-room-avatar.png"; - if (icon.save(iconPath)) + if (!iconPath.isNull()) templ.setImagePath(iconPath.toStdWString()); WinToast::instance()->showToast(templ, new CustomHandler()); From 8b33b1f08b941fe6ab5cf2431695284e21088223 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 26 Feb 2021 15:38:46 -0500 Subject: [PATCH 25/39] Simplify regex --- src/notifications/ManagerLinux.cpp | 2 +- src/notifications/ManagerMac.cpp | 2 +- src/notifications/ManagerWin.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 56fc8221..cd9d6fb8 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -234,7 +234,7 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ return QTextDocumentFragment::fromHtml( mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .replace(QRegularExpression(".+"), "")) .toPlainText() .prepend(messageLeadIn); } diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 973afdc4..8b6b3bd9 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -20,7 +20,7 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ return QTextDocumentFragment::fromHtml( mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .replace(QRegularExpression(".+"), "")) .toPlainText() .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) ? "* " + sender + " " diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 9c45d166..baafb6dc 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -121,7 +121,7 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ return QTextDocumentFragment::fromHtml( mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), "")) + .replace(QRegularExpression(".+"), "")) .toPlainText() .prepend(messageLeadIn); } From fda6d7629a7ebbcd1201a16e1be2bc21b1ca7dd9 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 1 Mar 2021 19:45:16 -0500 Subject: [PATCH 26/39] Switch readImage to take a reference instead of a pointer There was nowhere that an actual pointer was passed, and I wanted to do references for something else. --- src/AvatarProvider.cpp | 4 ++-- src/MxcImageProvider.cpp | 8 ++++---- src/Utils.cpp | 4 ++-- src/Utils.h | 2 +- src/timeline/InputBar.cpp | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp index 1834e040..f64f6859 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp @@ -35,7 +35,7 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca auto data = cache::image(cacheKey); if (!data.isNull()) { - pixmap = QPixmap::fromImage(utils::readImage(&data)); + pixmap = QPixmap::fromImage(utils::readImage(data)); avatar_cache.insert(cacheKey, pixmap); callback(pixmap); return; @@ -46,7 +46,7 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca &AvatarProxy::avatarDownloaded, receiver, [callback, cacheKey](QByteArray data) { - QPixmap pm = QPixmap::fromImage(utils::readImage(&data)); + QPixmap pm = QPixmap::fromImage(utils::readImage(data)); avatar_cache.insert(cacheKey, pm); callback(pm); }); diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index e4f629a5..35cd0c45 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -22,7 +22,7 @@ MxcImageResponse::run() auto data = cache::image(fileName); if (!data.isNull()) { - m_image = utils::readImage(&data); + m_image = utils::readImage(data); if (!m_image.isNull()) { m_image = m_image.scaled( @@ -54,7 +54,7 @@ MxcImageResponse::run() auto data = QByteArray(res.data(), (int)res.size()); cache::saveImage(fileName, data); - m_image = utils::readImage(&data); + m_image = utils::readImage(data); if (!m_image.isNull()) { m_image = m_image.scaled( m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); @@ -67,7 +67,7 @@ MxcImageResponse::run() auto data = cache::image(m_id); if (!data.isNull()) { - m_image = utils::readImage(&data); + m_image = utils::readImage(data); m_image.setText("mxc url", "mxc://" + m_id); if (!m_image.isNull()) { @@ -98,7 +98,7 @@ MxcImageResponse::run() auto data = QByteArray(temp.data(), (int)temp.size()); cache::saveImage(m_id, data); - m_image = utils::readImage(&data); + m_image = utils::readImage(data); m_image.setText("original filename", QString::fromStdString(originalFilename)); m_image.setText("mxc url", "mxc://" + m_id); diff --git a/src/Utils.cpp b/src/Utils.cpp index 40c16fd3..4d24c786 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -689,10 +689,10 @@ 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(); diff --git a/src/Utils.h b/src/Utils.h index 1dc5ffd6..eb09172e 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -309,7 +309,7 @@ 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/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 3827c91a..8a5e4346 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -574,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) From 82bbdfb92943cffcf62eee7bd8466765e40d5632 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 1 Mar 2021 19:56:11 -0500 Subject: [PATCH 27/39] Use better method of resizing images --- src/notifications/Manager.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 6fa06cb2..8a3576c5 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -49,22 +49,22 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents // delete any existing file content file.resize(0); - file.write(QByteArray(temp.data(), (int)temp.size())); - // resize the image (really inefficient, I know, but I can't find any - // better way right off - QImage img{path}; - - // delete existing contents - file.resize(0); + // resize the image + QImage img{utils::readImage(QByteArray{temp.data()})}; // make sure to save as PNG (because Plasma doesn't do JPEG in // notifications) // if (!file.fileName().endsWith(".png")) // file.rename(file.fileName() + ".png"); +#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(); return; From 64dd10a6a0066dc209196819534cc54c89628f33 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 1 Mar 2021 20:07:53 -0500 Subject: [PATCH 28/39] Only try to display images if they exist --- src/notifications/Manager.cpp | 11 ++++++----- src/notifications/ManagerLinux.cpp | 8 +++++++- src/notifications/ManagerMac.cpp | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 8a3576c5..b4a3a3da 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -24,7 +24,7 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents http::client()->download( url, - [path, url, encryptionInfo](const std::string &data, + [&path, url, encryptionInfo](const std::string &data, const std::string &, const std::string &, mtx::http::RequestErr err) { @@ -53,10 +53,11 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents // resize the image QImage img{utils::readImage(QByteArray{temp.data()})}; - // make sure to save as PNG (because Plasma doesn't do JPEG in - // notifications) - // if (!file.fileName().endsWith(".png")) - // file.rename(file.fileName() + ".png"); + if (img.isNull()) + { + path.clear(); + return; + } #ifdef NHEKO_DBUS_SYS // the images in D-Bus notifications are to be 200x100 max img.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index cd9d6fb8..0cf61d5c 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -216,12 +216,18 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ 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( - "\""") .prepend(messageLeadIn); + } return mtx::accessors::formattedBodyWithFallback(notification.event) .prepend(messageLeadIn) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 8b6b3bd9..7e3ad309 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -58,6 +58,6 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const QString messageInfo = (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender); objCxxPostNotification( - room_name, messageInfo, formatNotification(notification), image); + room_name, messageInfo, formatNotification(notification), (image != nullptr && !image->isNull()) ? image : nullptr); } } From 98b2fee71b3a07ec21f6010a50e4809031781829 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 2 Mar 2021 19:42:34 -0500 Subject: [PATCH 29/39] Block notifications until the image has been downloaded --- src/notifications/Manager.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index b4a3a3da..30e74d33 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -22,17 +22,22 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + filename}; + bool downloadComplete = false; + http::client()->download( url, - [&path, url, encryptionInfo](const std::string &data, - const std::string &, - const std::string &, - mtx::http::RequestErr err) { + [&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(err->status_code)); + // the image doesn't exist, so delete the path + path.clear(); + downloadComplete = true; return; } @@ -44,8 +49,11 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents QFile file{path}; - if (!file.open(QIODevice::WriteOnly)) + if (!file.open(QIODevice::WriteOnly)) { + path.clear(); + downloadComplete = true; return; + } // delete any existing file content file.resize(0); @@ -53,10 +61,10 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents // resize the image QImage img{utils::readImage(QByteArray{temp.data()})}; - if (img.isNull()) - { - path.clear(); - return; + if (img.isNull()) { + path.clear(); + downloadComplete = true; + return; } #ifdef NHEKO_DBUS_SYS // the images in D-Bus notifications are to be 200x100 max @@ -68,11 +76,15 @@ NotificationsManager::cacheImage(const mtx::events::collections::TimelineEvents 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(); } From 5da6ab0aec1cc397aed9f54e3d3525520212447f Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 2 Mar 2021 19:42:56 -0500 Subject: [PATCH 30/39] make lint --- src/notifications/ManagerLinux.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 0cf61d5c..5581252d 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -214,19 +214,18 @@ NotificationsManager::formatNotification(const mtx::responses::Notification ¬ ": "); 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( - "\""") - .prepend(messageLeadIn); + 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("\""") + .prepend(messageLeadIn); } return mtx::accessors::formattedBodyWithFallback(notification.event) From 716c598f4aa9182b783134b1c3f5da60903c9a8c Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 2 Mar 2021 19:43:25 -0500 Subject: [PATCH 31/39] Simplify macOS checks for a null image --- src/notifications/Manager.h | 2 ++ src/notifications/ManagerMac.cpp | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index d7820b18..825da4c8 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -80,6 +80,8 @@ private: const QString &subtitle, const QString &informativeText, const QImage *bodyImage); + + QImage *getImgOrNullptr(const QString &path); #endif #if defined(Q_OS_WINDOWS) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 7e3ad309..0819272b 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -41,7 +41,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if QImage *image = nullptr; if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) - image = new QImage{cacheImage(notification.event)}; + image = getImgOrNullptr(cacheImage(notification.event)); const auto isEncrypted = std::get_if>( @@ -58,6 +58,17 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const QString messageInfo = (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender); objCxxPostNotification( - room_name, messageInfo, formatNotification(notification), (image != nullptr && !image->isNull()) ? image : nullptr); + room_name, messageInfo, formatNotification(notification), image); } } + +QImage * +NotificationsManager::getImgOrNullptr(const QString &path) +{ + auto img = new QImage{path}; + if (img->isNull()) { + delete img; + return nullptr; + } + return img; +} From 95a26edad25fe7873076f06fcb902e90dcf83aac Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 2 Mar 2021 19:51:44 -0500 Subject: [PATCH 32/39] Don't create a QImage every time --- src/notifications/ManagerMac.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 0819272b..1bf1577f 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -65,10 +65,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if QImage * NotificationsManager::getImgOrNullptr(const QString &path) { - auto img = new QImage{path}; - if (img->isNull()) { - delete img; + if (QFile::exists(path)) return nullptr; - } - return img; + return new QImage{path}; } From 41737ac22c3689324e4296d6fdfc9677a86d70e6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 2 Mar 2021 20:35:03 -0500 Subject: [PATCH 33/39] Simplify image loading --- src/notifications/Manager.h | 4 +--- src/notifications/ManagerMac.cpp | 12 ++---------- src/notifications/ManagerMac.mm | 6 +++--- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index 825da4c8..a1ef9f98 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -79,9 +79,7 @@ private: void objCxxPostNotification(const QString &title, const QString &subtitle, const QString &informativeText, - const QImage *bodyImage); - - QImage *getImgOrNullptr(const QString &path); + const QImage &bodyImage); #endif #if defined(Q_OS_WINDOWS) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 1bf1577f..de5d0875 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -39,9 +39,9 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); - QImage *image = nullptr; + QImage image; if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) - image = getImgOrNullptr(cacheImage(notification.event)); + image = QImage{cacheImage(notification.event)}; const auto isEncrypted = std::get_if>( @@ -61,11 +61,3 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if room_name, messageInfo, formatNotification(notification), image); } } - -QImage * -NotificationsManager::getImgOrNullptr(const QString &path) -{ - if (QFile::exists(path)) - return nullptr; - return new QImage{path}; -} diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index 226bcce0..33b7b6af 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -19,7 +19,7 @@ void NotificationsManager::objCxxPostNotification(const QString &title, const QString &subtitle, const QString &informativeText, - const QImage *bodyImage) + const QImage &bodyImage) { NSUserNotification *notif = [[NSUserNotification alloc] init]; @@ -29,8 +29,8 @@ NotificationsManager::objCxxPostNotification(const QString &title, notif.informativeText = informativeText.toNSString(); notif.soundName = NSUserNotificationDefaultSoundName; - if (bodyImage != nullptr) - notif.contentImage = [[NSImage alloc] initWithCGImage: bodyImage->toCGImage() size: NSZeroSize]; + if (!bodyImage.isNull()) + notif.contentImage = [[NSImage alloc] initWithCGImage: bodyImage.toCGImage() size: NSZeroSize]; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif]; [notif autorelease]; From 95026dcc62d4ba0ea0707c75bbc54f1ac01fb954 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 03:27:28 +0100 Subject: [PATCH 34/39] Refactor image download code to be reusable --- src/MxcImageProvider.cpp | 220 +++++++++++++++++++++++++++------------ src/MxcImageProvider.h | 31 ++---- 2 files changed, 161 insertions(+), 90 deletions(-) diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 35cd0c45..db0f72c9 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -4,106 +4,192 @@ #include "MxcImageProvider.h" +#include + #include +#include +#include +#include + #include "Cache.h" #include "Logging.h" #include "MatrixClient.h" #include "Utils.h" +QHash 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(); + }); +} - auto data = cache::image(fileName); - if (!data.isNull()) { - m_image = utils::readImage(data); +void +MxcImageProvider::download(const QString &id, + const QSize &requestedSize, + std::function then) +{ + std::optional encryptionInfo; + auto temp = infos.find("mxc://" + id); + if (temp != infos.end()) + encryptionInfo = *temp; - if (!m_image.isNull()) { - m_image = m_image.scaled( - m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - m_image.setText("mxc url", "mxc://" + m_id); + if (requestedSize.isValid() && !encryptionInfo) { + QString fileName = + QString("%1_%2x%3_crop") + .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | + QByteArray::OmitTrailingEquals)), + requestedSize.width(), + requestedSize.height()); + QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + + "/media_cache", + fileName); - if (!m_image.isNull()) { - emit finished(); + if (fileInfo.exists()) { + QImage image(fileInfo.absoluteFilePath()); + if (!image.isNull()) { + image = image.scaled( + requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + 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); + image.save(fileInfo.absoluteFilePath()); - 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); - 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 temp = + mtx::crypto::to_string(mtx::crypto::decrypt_file( + fileData.toStdString(), encryptionInfo.value())); + auto data = QByteArray(temp.data(), (int)temp.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; + } + } } + auto data = cache::image(id); + + 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; + } + + auto temp = res; + QFile f(fileInfo.absoluteFilePath()); + if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + then(id, QSize(), {}, ""); + return; + } + f.write(temp.data(), temp.size()); + f.close(); + + if (encryptionInfo) { + temp = mtx::crypto::to_string(mtx::crypto::decrypt_file( + temp, encryptionInfo.value())); + auto data = QByteArray(temp.data(), (int)temp.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; + } + + 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()); } - - 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(); - - return; - } - - auto temp = res; - if (m_encryptionInfo) - temp = mtx::crypto::to_string( - mtx::crypto::decrypt_file(temp, m_encryptionInfo.value())); - - 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); - - emit finished(); - }); } } 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 #include -#include +#include -#include +#include class MxcImageResponse : public QQuickImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, - const QSize &requestedSize, - boost::optional 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 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 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 then); private: QThreadPool pool; - QHash infos; }; From e5d75c814b2175dc37beabff3b0421de59a3e93e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:08:17 +0100 Subject: [PATCH 35/39] Clean up notification code a bit --- src/AvatarProvider.cpp | 65 +++++------- src/AvatarProvider.h | 8 +- src/Cache.cpp | 161 +---------------------------- src/Cache.h | 22 ---- src/CacheStructs.h | 1 - src/Cache_p.h | 10 -- src/CommunitiesList.cpp | 36 ++----- src/MxcImageProvider.cpp | 18 ++-- src/Utils.cpp | 29 ++++++ src/Utils.h | 3 + src/notifications/Manager.cpp | 104 +++++-------------- src/notifications/Manager.h | 12 ++- src/notifications/ManagerLinux.cpp | 126 +++++++++++----------- src/notifications/ManagerMac.cpp | 37 ++++--- src/notifications/ManagerWin.cpp | 21 ++-- src/timeline/TimelineModel.cpp | 25 +---- 16 files changed, 204 insertions(+), 474 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; - } + MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")), + QSize(size, size), + [callback, cacheKey, recv = QPointer(receiver)]( + QString, QSize, QImage img, QString) { + if (!recv) + return; - auto proxy = std::make_shared(); - QObject::connect(proxy.get(), - &AvatarProxy::avatarDownloaded, - receiver, - [callback, cacheKey](QByteArray data) { - QPixmap pm = QPixmap::fromImage(utils::readImage(data)); - avatar_cache.insert(cacheKey, pm); - callback(pm); - }); + auto proxy = std::make_shared(); + QObject::connect(proxy.get(), + &AvatarProxy::avatarDownloaded, + recv, + [callback, cacheKey](QPixmap pm) { + if (!pm.isNull()) + avatar_cache.insert( + cacheKey, pm); + callback(pm); + }); - mtx::http::ThumbOpts opts; - opts.width = size; - opts.height = size; - opts.mxc_url = avatarUrl.toStdString(); + if (img.isNull()) { + emit proxy->avatarDownloaded(QPixmap{}); + return; + } - 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); - } - - 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 #include +using AvatarCallback = std::function; + class AvatarProxy : public QObject { Q_OBJECT signals: - void avatarDownloaded(const QByteArray &data); + void avatarDownloaded(QPixmap pm); }; -using AvatarCallback = std::function; - 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 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 &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 -#include -#include #include #if __has_include() @@ -135,12 +133,6 @@ hasEnoughPowerLevel(const std::vector &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> 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 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>; 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 roomsWithStateUpdates(const mtx::responses::Sync &res); std::vector 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 #include +#include #include #include -#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( + ".*", 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 -#include -#include - -#include - 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); + const auto sender = + cache::displayName(QString::fromStdString(notification.room_id), + QString::fromStdString(mtx::accessors::sender(notification.event))); - auto filename = QString::fromStdString(mtx::accessors::body(event)); - QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + - filename}; + // TODO: decrypt this message if the decryption setting is on in the UserSettings + if (auto msg = std::get_if>( + ¬ification.event); + msg != nullptr) { + return tr("%1 sent an encrypted message").arg(sender); + } - 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(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(); + 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 #include #include +#include #include #include @@ -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>( + 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("", "") + .replace("", "") + .replace("", "") + .replace("", "")); + else + postNotif(template_.arg( + QStringLiteral("
\""")); + }); + return; + } + + postNotif( + template_ + .arg( + utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body) + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", "")); + return; + } + + postNotif( + template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body)); } /** @@ -182,68 +242,6 @@ NotificationsManager::notificationClosed(uint id, uint reason) notificationIds.remove(id); } -/** - * @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>( - ¬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("\""") - .prepend(messageLeadIn); - } - - return mtx::accessors::formattedBodyWithFallback(notification.event) - .prepend(messageLeadIn) - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace(QRegularExpression("(.+\\<\\/mx-reply\\>)"), ""); - } - - return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression(".+"), "")) - .toPlainText() - .prepend(messageLeadIn); -} - /** * Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify * 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 @@ -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(".+"), "")) - .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>( ¬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'.") - : "") + - ": "); + const auto template_ = getMessageTemplate(notification); + if (std::holds_alternative>( + notification.event)) { + return template_; + } - return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression(".+"), "")) - .toPlainText() - .prepend(messageLeadIn); + 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( - ".*", QRegularExpression::DotMatchesEverythingOption)); - related.quoted_formatted_body.replace("@room", "@\u2060aroom"); - related.room = room_id_; - - return related; + return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_); } void From f6d2fa5ec17f54a456a4707a61a88e36827274b2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:14:25 +0100 Subject: [PATCH 36/39] Fix licenses --- src/notifications/Manager.cpp | 4 ++++ src/notifications/ManagerLinux.cpp | 5 +++++ src/notifications/ManagerMac.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index 322213dd..be580b08 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #include "notifications/Manager.h" #include "Cache.h" diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 2b0e56e2..598b2bd0 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -1,3 +1,8 @@ +// SPDX-FileCopyrightText: 2012 Roland Hieber +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #include "notifications/Manager.h" #include diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index 3a6becad..be56585c 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #include "Manager.h" #include From 95bbc559fad84df869b02412c27be8c9af959d0e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:45:02 +0100 Subject: [PATCH 37/39] Add missing QPointer include --- src/AvatarProvider.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp index 8cc1144f..b9962cef 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include From 21562eed759563d70561f630b481e8bc47651052 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 20:32:12 +0100 Subject: [PATCH 38/39] Fix shadowing --- src/MxcImageProvider.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 023d0e57..a20657c8 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -128,10 +128,11 @@ MxcImageProvider::download(const QString &id, f.open(QIODevice::ReadOnly); QByteArray fileData = f.readAll(); - auto temp = + auto tempData = mtx::crypto::to_string(mtx::crypto::decrypt_file( fileData.toStdString(), encryptionInfo.value())); - auto data = QByteArray(temp.data(), (int)temp.size()); + auto data = + QByteArray(tempData.data(), (int)tempData.size()); QImage image = utils::readImage(data); image.setText("mxc url", "mxc://" + id); if (!image.isNull()) { @@ -165,19 +166,21 @@ MxcImageProvider::download(const QString &id, return; } - auto temp = res; + auto tempData = res; QFile f(fileInfo.absoluteFilePath()); if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { then(id, QSize(), {}, ""); return; } - f.write(temp.data(), temp.size()); + f.write(tempData.data(), tempData.size()); f.close(); if (encryptionInfo) { - temp = mtx::crypto::to_string(mtx::crypto::decrypt_file( - temp, encryptionInfo.value())); - auto data = QByteArray(temp.data(), (int)temp.size()); + 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)); From 1408b1a97d37a732ec06916489a60cd9e4d9f6dd Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 22:13:12 +0100 Subject: [PATCH 39/39] Make CI happy --- src/notifications/ManagerMac.cpp | 4 ++-- src/notifications/ManagerWin.cpp | 30 +++++++++++++----------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index be56585c..8e36985c 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -16,8 +16,8 @@ #include -QString -NotificationsManager::formatNotification(const mtx::responses::Notification ¬ification) +static QString +formatNotification(const mtx::responses::Notification ¬ification) { return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body; } diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index d37bff67..fe7830a7 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -60,11 +60,23 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if ¬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>( + 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(notification)); + : formatNotification()); auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + room_name + "-room-avatar.png"; @@ -100,19 +112,3 @@ void NotificationsManager::notificationClosed(uint, uint) {} void NotificationsManager::removeNotification(const QString &, const QString &) {} - -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))); - - const auto template_ = getMessageTemplate(notification); - if (std::holds_alternative>( - notification.event)) { - return template_; - } - - return template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body); -}