mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Rewrite notification posting logic
This does away with the nice abstraction layers in order to easily get the best-looking notifications for each platform.
This commit is contained in:
parent
37acdad928
commit
f578272a0d
6 changed files with 261 additions and 83 deletions
|
@ -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 auto url = mtx::accessors::url(event);
|
||||
auto encryptionInfo = mtx::accessors::file(event);
|
||||
|
||||
const QString reply = (utils::isReply(notification.event)
|
||||
? tr(" replied",
|
||||
"Used to denote that this message is a reply to another "
|
||||
"message. Displayed as 'foo replied: message'.")
|
||||
: "");
|
||||
auto filename = QString::fromStdString(mtx::accessors::body(event));
|
||||
QString path{QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" +
|
||||
filename};
|
||||
|
||||
// the "replied" is only added if this message is not an emote message
|
||||
QString text =
|
||||
((mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote)
|
||||
? "* " + sender + " "
|
||||
: sender + reply + ": ") +
|
||||
formatNotification(notification.event);
|
||||
|
||||
systemPostNotification(room_id, event_id, room_name, sender, text, icon);
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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 cacheImage(const mtx::events::collections::TimelineEvents &event);
|
||||
QString formatNotification(const mtx::responses::Notification ¬ification);
|
||||
|
||||
QString formatNotification(const mtx::events::collections::TimelineEvents &e);
|
||||
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU)
|
||||
#if defined(NHEKO_DBUS_SYS)
|
||||
public:
|
||||
void closeNotifications(QString roomId);
|
||||
|
||||
private:
|
||||
QDBusInterface dbus;
|
||||
|
||||
void systemPostNotification(const QString &room_id,
|
||||
const QString &event_id,
|
||||
const QString &roomName,
|
||||
const QString &text,
|
||||
const QImage &icon);
|
||||
void closeNotification(uint id);
|
||||
|
||||
// notification ID to (room ID, event ID)
|
||||
QMap<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 &
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
||||
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];
|
||||
}
|
||||
|
|
|
@ -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 + " "
|
||||
: "");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue