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 ¬ification,
- 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 ¬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<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 ¬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 <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 ¬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(
+ "<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 ¬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>.+\\<\\/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 ¬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 <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 ¬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>.+\\<\\/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 + " "
+ : "");
}
|