From 648844089c1786f1439ea026d1f3f5d0b757a414 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 15 Feb 2021 18:36:10 -0500 Subject: Move data parsing into a dedicated function Actually posting the notification is now the responsibility of a private function --- src/notifications/Manager.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/notifications/Manager.cpp (limited to 'src/notifications/Manager.cpp') 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); +} -- cgit 1.5.1 From b05657d51af7fa18b96eb664b353d620f5b6f104 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 19 Feb 2021 19:31:48 -0500 Subject: Fix colon spacing --- src/notifications/Manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/notifications/Manager.cpp') 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); } -- cgit 1.5.1 From 4150d75be7c8945808434d67bd23bb688707af29 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 11:57:42 -0500 Subject: Only HTML-format the body if it should be formatted --- src/notifications/Manager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/notifications/Manager.cpp') 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); } -- cgit 1.5.1 From d8fb4d92929eefa1a0d982bac2bcfd7db21fcb7e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 12:06:43 -0500 Subject: Simplify message body construction --- src/notifications/Manager.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/notifications/Manager.cpp') 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); } -- cgit 1.5.1 From b57b76d948407e36f05de51d80289dc82bf63464 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:10:14 -0500 Subject: Add "replied" marker to regular reply messages --- src/notifications/Manager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/notifications/Manager.cpp') 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); -- cgit 1.5.1 From df998ef67194bdd633df163eef53ec628fc8e090 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:16:43 -0500 Subject: 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(-) (limited to 'src/notifications/Manager.cpp') 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(); } + -- cgit 1.5.1 From c693d545985a3e142bb3d8f30cc6f47988488142 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 20 Feb 2021 13:58:04 -0500 Subject: Fix when "replied" is displayed I accidentally put it in backwards. --- src/notifications/Manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/notifications/Manager.cpp') 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 = -- cgit 1.5.1 From f578272a0d645bcfae5d70f6e4aa1dc4649511f1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:17:57 +0100 Subject: 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 | 94 ++++++++++++++++++++++++++++---------- 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, 264 insertions(+), 86 deletions(-) (limited to 'src/notifications/Manager.cpp') 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 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 + reply + ": ") + - formatNotification(notification.event); - - systemPostNotification(room_id, event_id, room_name, sender, text, icon); + const auto url = mtx::accessors::url(event); + auto encryptionInfo = mtx::accessors::file(event); + + auto filename = QString::fromStdString(mtx::accessors::body(event)); + QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + + filename}; + + 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; + } + + 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 formatNotification(const mtx::events::collections::TimelineEvents &e); + QString cacheImage(const mtx::events::collections::TimelineEvents &event); + QString formatNotification(const mtx::responses::Notification ¬ification); -#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 + " " + : ""); } -- cgit 1.5.1 From 82bbdfb92943cffcf62eee7bd8466765e40d5632 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 1 Mar 2021 19:56:11 -0500 Subject: Use better method of resizing images --- src/notifications/Manager.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/notifications/Manager.cpp') 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; -- cgit 1.5.1 From 64dd10a6a0066dc209196819534cc54c89628f33 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 1 Mar 2021 20:07:53 -0500 Subject: 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(-) (limited to 'src/notifications/Manager.cpp') 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); } } -- cgit 1.5.1 From 98b2fee71b3a07ec21f6010a50e4809031781829 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 2 Mar 2021 19:42:34 -0500 Subject: Block notifications until the image has been downloaded --- src/notifications/Manager.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'src/notifications/Manager.cpp') 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(); } -- cgit 1.5.1 From e5d75c814b2175dc37beabff3b0421de59a3e93e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:08:17 +0100 Subject: 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 | 108 +++++++------------------ src/notifications/Manager.h | 12 ++- src/notifications/ManagerLinux.cpp | 126 ++++++++++++++--------------- src/notifications/ManagerMac.cpp | 37 +++++---- src/notifications/ManagerWin.cpp | 23 ++---- src/timeline/TimelineModel.cpp | 25 +----- 16 files changed, 207 insertions(+), 477 deletions(-) (limited to 'src/notifications/Manager.cpp') diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp index f64f6859..8cc1144f 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp @@ -12,13 +12,14 @@ #include "Cache.h" #include "Logging.h" #include "MatrixClient.h" +#include "MxcImageProvider.h" #include "Utils.h" static QPixmapCache avatar_cache; namespace AvatarProvider { void -resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback) +resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback) { const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size); @@ -33,44 +34,32 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca return; } - auto data = cache::image(cacheKey); - if (!data.isNull()) { - pixmap = QPixmap::fromImage(utils::readImage(data)); - avatar_cache.insert(cacheKey, pixmap); - callback(pixmap); - return; - } - - auto proxy = std::make_shared(); - QObject::connect(proxy.get(), - &AvatarProxy::avatarDownloaded, - receiver, - [callback, cacheKey](QByteArray data) { - QPixmap pm = QPixmap::fromImage(utils::readImage(data)); - avatar_cache.insert(cacheKey, pm); - callback(pm); - }); + MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")), + QSize(size, size), + [callback, cacheKey, recv = QPointer(receiver)]( + QString, QSize, QImage img, QString) { + if (!recv) + return; - mtx::http::ThumbOpts opts; - opts.width = size; - opts.height = size; - opts.mxc_url = avatarUrl.toStdString(); + auto proxy = std::make_shared(); + QObject::connect(proxy.get(), + &AvatarProxy::avatarDownloaded, + recv, + [callback, cacheKey](QPixmap pm) { + if (!pm.isNull()) + avatar_cache.insert( + cacheKey, pm); + callback(pm); + }); - http::client()->get_thumbnail( - opts, - [opts, cacheKey, proxy = std::move(proxy)](const std::string &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to download avatar: {} - ({} {})", - opts.mxc_url, - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - } else { - cache::saveImage(cacheKey.toStdString(), res); - } + if (img.isNull()) { + emit proxy->avatarDownloaded(QPixmap{}); + return; + } - emit proxy->avatarDownloaded(QByteArray(res.data(), (int)res.size())); - }); + auto pm = QPixmap::fromImage(std::move(img)); + emit proxy->avatarDownloaded(pm); + }); } void @@ -80,8 +69,8 @@ resolve(const QString &room_id, QObject *receiver, AvatarCallback callback) { - const auto avatarUrl = cache::avatarUrl(room_id, user_id); + auto avatarUrl = cache::avatarUrl(room_id, user_id); - resolve(avatarUrl, size, receiver, callback); + resolve(std::move(avatarUrl), size, receiver, callback); } } diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h index 0bea1a8f..173a2fba 100644 --- a/src/AvatarProvider.h +++ b/src/AvatarProvider.h @@ -8,19 +8,19 @@ #include #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); - - auto filename = QString::fromStdString(mtx::accessors::body(event)); - QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + - filename}; - - bool downloadComplete = false; - - http::client()->download( - url, - [&downloadComplete, &path, url, encryptionInfo](const std::string &data, - const std::string &, - const std::string &, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve image {}: {} {}", - url, - err->matrix_error.error, - static_cast(err->status_code)); - // the image doesn't exist, so delete the path - path.clear(); - downloadComplete = true; - return; - } - - try { - auto temp = data; - if (encryptionInfo) - temp = mtx::crypto::to_string( - mtx::crypto::decrypt_file(temp, encryptionInfo.value())); - - QFile file{path}; - - if (!file.open(QIODevice::WriteOnly)) { - path.clear(); - downloadComplete = true; - return; - } - - // delete any existing file content - file.resize(0); - - // resize the image - QImage img{utils::readImage(QByteArray{temp.data()})}; - - if (img.isNull()) { - path.clear(); - downloadComplete = true; - return; - } - -#ifdef NHEKO_DBUS_SYS // the images in D-Bus notifications are to be 200x100 max - img.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation) - .save(&file); -#else - img.save(&file); -#endif // NHEKO_DBUS_SYS - - file.close(); - - downloadComplete = true; - return; - } catch (const std::exception &e) { - nhlog::ui()->warn("Error while caching file to: {}", e.what()); - } - }); - - while (!downloadComplete) - continue; - - return path.toHtmlEscaped(); + const auto sender = + cache::displayName(QString::fromStdString(notification.room_id), + QString::fromStdString(mtx::accessors::sender(notification.event))); + + // TODO: decrypt this message if the decryption setting is on in the UserSettings + if (auto msg = std::get_if>( + ¬ification.event); + msg != nullptr) { + return tr("%1 sent an encrypted message").arg(sender); + } + + if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) { + return tr("* %1 %2", + "Format an emote message in a notification, %1 is the sender, %2 the " + "message") + .arg(sender); + } else if (utils::isReply(notification.event)) { + return tr("%1 replied: %2", + "Format a reply in a notification. %1 is the sender, %2 the message") + .arg(sender); + } else { + return tr("%1: %2", + "Format a normal message in a notification. %1 is the sender, %2 the " + "message") + .arg(sender); + } } diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index a1ef9f98..416530e0 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -43,14 +43,15 @@ public: signals: void notificationClicked(const QString roomId, const QString eventId); void sendNotificationReply(const QString roomId, const QString eventId, const QString body); + void systemPostNotificationCb(const QString &room_id, + const QString &event_id, + const QString &roomName, + const QString &text, + const QImage &icon); public slots: void removeNotification(const QString &roomId, const QString &eventId); -private: - QString cacheImage(const mtx::events::collections::TimelineEvents &event); - QString formatNotification(const mtx::responses::Notification ¬ification); - #if defined(NHEKO_DBUS_SYS) public: void closeNotifications(QString roomId); @@ -95,6 +96,9 @@ private slots: void actionInvoked(uint id, QString action); void notificationClosed(uint id, uint reason); void notificationReplied(uint id, QString reply); + +private: + QString getMessageTemplate(const mtx::responses::Notification ¬ification); }; #if defined(NHEKO_DBUS_SYS) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 5581252d..2b0e56e2 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -8,6 +8,7 @@ #include #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'.") - : "") + - ": "); - - return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(notification.event) - .replace(QRegularExpression(".+"), "")) - .toPlainText() - .prepend(messageLeadIn); + const auto template_ = getMessageTemplate(notification); + if (std::holds_alternative>( + notification.event)) { + return template_; + } + + return template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body); } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index cfca626a..8e96cb3e 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -870,30 +870,7 @@ TimelineModel::relatedInfo(QString id) if (!event) return {}; - RelatedInfo related = {}; - related.quoted_user = QString::fromStdString(mtx::accessors::sender(*event)); - related.related_event = id.toStdString(); - related.type = mtx::accessors::msg_type(*event); - - // get body, strip reply fallback, then transform the event to text, if it is a media event - // etc - related.quoted_body = QString::fromStdString(mtx::accessors::body(*event)); - QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); - while (related.quoted_body.startsWith(">")) - related.quoted_body.remove(plainQuote); - if (related.quoted_body.startsWith("\n")) - related.quoted_body.remove(0, 1); - related.quoted_body = utils::getQuoteBody(related); - related.quoted_body.replace("@room", QString::fromUtf8("@\u2060room")); - - // get quoted body and strip reply fallback - related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(*event); - related.quoted_formatted_body.remove(QRegularExpression( - ".*", QRegularExpression::DotMatchesEverythingOption)); - related.quoted_formatted_body.replace("@room", "@\u2060aroom"); - related.room = room_id_; - - return related; + return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_); } void -- cgit 1.5.1 From f6d2fa5ec17f54a456a4707a61a88e36827274b2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Mar 2021 19:14:25 +0100 Subject: Fix licenses --- src/notifications/Manager.cpp | 4 ++++ src/notifications/ManagerLinux.cpp | 5 +++++ src/notifications/ManagerMac.cpp | 4 ++++ 3 files changed, 13 insertions(+) (limited to 'src/notifications/Manager.cpp') 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 -- cgit 1.5.1