summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2021-03-17 19:17:57 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2021-03-17 19:17:57 +0100
commitf578272a0d645bcfae5d70f6e4aa1dc4649511f1 (patch)
tree938cd7acff3b28bdc9a49a352be2998ab079d753 /src
parentAdd regex to remove replies in notifications (diff)
downloadnheko-f578272a0d645bcfae5d70f6e4aa1dc4649511f1.tar.xz
Rewrite notification posting logic
This does away with the nice abstraction layers in order to easily get the best-looking notifications for each platform.
Diffstat (limited to 'src')
-rw-r--r--src/notifications/Manager.cpp94
-rw-r--r--src/notifications/Manager.h46
-rw-r--r--src/notifications/ManagerLinux.cpp83
-rw-r--r--src/notifications/ManagerMac.cpp50
-rw-r--r--src/notifications/ManagerMac.mm31
-rw-r--r--src/notifications/ManagerWin.cpp46
6 files changed, 264 insertions, 86 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 <mtx/responses/notifications.hpp> -void -NotificationsManager::postNotification(const mtx::responses::Notification &notification, - const QImage &icon) +#include <QFile> +#include <QImage> +#include <QStandardPaths> + +#include <mtxclient/crypto/client.hpp> + +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<int>(err->status_code)); + return; + } + + try { + auto temp = data; + if (encryptionInfo) + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + + QFile file{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 <mtx/responses/notifications.hpp> +// convenience definition #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU) +#define NHEKO_DBUS_SYS +#endif + +#if defined(NHEKO_DBUS_SYS) #include <QtDBus/QDBusArgument> #include <QtDBus/QDBusInterface> #endif @@ -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 &notification); -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU) +#if defined(NHEKO_DBUS_SYS) public: void closeNotifications(QString roomId); private: QDBusInterface dbus; + + void systemPostNotification(const QString &room_id, + const QString &event_id, + const QString &roomName, + const QString &text, + const QImage &icon); void closeNotification(uint id); // notification ID to (room ID, event ID) QMap<uint, roomEventId> notificationIds; + + const bool hasMarkup_; + const bool hasImages_; +#endif + +#if defined(Q_OS_MACOS) +private: + // Objective-C(++) doesn't like to do lots of regular C++, so the actual notification + // posting is split out + void objCxxPostNotification(const QString &title, + const QString &subtitle, + const QString &informativeText, + const QImage *bodyImage); +#endif + +#if defined(Q_OS_WINDOWS) +private: + void systemPostNotification(const QString &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 <functional> +#include <mtx/responses/notifications.hpp> + +#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<QImage>(); @@ -45,21 +60,32 @@ NotificationsManager::NotificationsManager(QObject *parent) SLOT(notificationReplied(uint, QString))); } -// SPDX-FileCopyrightText: 2012 Roland Hieber <rohieb@rohieb.name> -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later +void +NotificationsManager::postNotification(const mtx::responses::Notification &notification, + 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 <rohieb@rohieb.name> + * Licensed under the GNU General Public License, version 3 + */ void NotificationsManager::systemPostNotification(const QString &room_id, const QString &event_id, const QString &roomName, - const QString &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 &notification) { - 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( + "<img src=\"file:///" + cacheImage(notification.event) + + "\" alt=\"" + + mtx::accessors::formattedBodyWithFallback(notification.event) + + "\">") + .prepend(messageLeadIn); + + return mtx::accessors::formattedBodyWithFallback(notification.event) + .prepend(messageLeadIn) .replace("<em>", "<i>") .replace("</em>", "</i>") .replace("<strong>", "<b>") .replace("</strong>", "</b>") .replace(QRegularExpression("(<mx-reply>.+\\<\\/mx-reply\\>)"), ""); + } return QTextDocumentFragment::fromHtml( - mtx::accessors::formattedBodyWithFallback(e).replace( - QRegularExpression("(<mx-reply>.+\\<\\/mx-reply\\>)"), "")) - .toPlainText(); + mtx::accessors::formattedBodyWithFallback(notification.event) + .replace(QRegularExpression("(<mx-reply>.+\\<\\/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 <QRegularExpression> #include <QTextDocumentFragment> +#include "Cache.h" #include "EventAccessors.h" #include "Utils.h" +#include <mtx/responses/notifications.hpp> + QString -NotificationsManager::formatNotification(const mtx::events::collections::TimelineEvents &e) +NotificationsManager::formatNotification(const mtx::responses::Notification &notification) { + 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>.+\\<\\/mx-reply\\>)"), "")) - .toPlainText(); + mtx::accessors::formattedBodyWithFallback(notification.event) + .replace(QRegularExpression("(<mx-reply>.+\\<\\/mx-reply\\>)"), "")) + .toPlainText() + .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) + ? "* " + sender + " " + : ""); +} + +void +NotificationsManager::postNotification(const mtx::responses::Notification &notification, + 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 <Foundation/Foundation.h> +#import <Foundation/Foundation.h> +#import <AppKit/NSImage.h> + #include <QtMac> +#include <QImage> @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 <QRegularExpression> +#include <QStandardPaths> #include <QTextDocumentFragment> +#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 &notification, + 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 &notification) { + 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>.+\\<\\/mx-reply\\>)"), "")) - .toPlainText(); + mtx::accessors::formattedBodyWithFallback(notification.event) + .replace(QRegularExpression("(<mx-reply>.+\\<\\/mx-reply\\>)"), "")) + .toPlainText() + .prepend((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) + ? "* " + sender + " " + : ""); }