Working D-Bus desktop notifications (#361)

* Working D-Bus desktop notifications

* Remove return type on constructor

* Fix the Windows placeholder class

* Fix wrong variable name

* Fix windows and macOS versions of notificationsmanager
This commit is contained in:
Max Sandholm 2018-07-11 17:33:02 +03:00 committed by mujx
parent e7f30b57e8
commit 80ebe3f29d
7 changed files with 286 additions and 18 deletions

View file

@ -67,6 +67,7 @@ include(LMDB)
# Discover Qt dependencies. # Discover Qt dependencies.
# #
find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia REQUIRED) find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia REQUIRED)
find_package(Qt5DBus)
if (APPLE) if (APPLE)
find_package(Qt5MacExtras REQUIRED) find_package(Qt5MacExtras REQUIRED)
@ -304,6 +305,8 @@ qt5_wrap_cpp(MOC_HEADERS
include/ui/Theme.h include/ui/Theme.h
include/ui/ThemeManager.h include/ui/ThemeManager.h
include/notifications/Manager.h
include/AvatarProvider.h include/AvatarProvider.h
include/Cache.h include/Cache.h
include/ChatPage.h include/ChatPage.h
@ -386,7 +389,7 @@ elseif(WIN32)
target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain) target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain)
else() else()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS}) target_link_libraries (nheko ${NHEKO_LIBS} Qt5::DBus)
endif() endif()
if(EXTERNAL_PROJECT_DEPS) if(EXTERNAL_PROJECT_DEPS)

View file

@ -30,6 +30,7 @@
#include "CommunitiesList.h" #include "CommunitiesList.h"
#include "Community.h" #include "Community.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "notifications/Manager.h"
class OverlayModal; class OverlayModal;
class QuickSwitcher; class QuickSwitcher;
@ -42,6 +43,7 @@ class TopRoomBar;
class TypingDisplay; class TypingDisplay;
class UserInfoWidget; class UserInfoWidget;
class UserSettings; class UserSettings;
class NotificationsManager;
namespace dialogs { namespace dialogs {
class ReadReceipts; class ReadReceipts;
@ -140,6 +142,13 @@ signals:
void syncTopBar(const std::map<QString, RoomInfo> &updates); void syncTopBar(const std::map<QString, RoomInfo> &updates);
void dropToLoginPageCb(const QString &msg); void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid,
const QString &eventid,
const QString &roomname,
const QString &sender,
const QString &message,
const QImage &icon);
private slots: private slots:
void showUnreadMessageNotification(int count); void showUnreadMessageNotification(int count);
void updateTopBarAvatar(const QString &roomid, const QPixmap &img); void updateTopBarAvatar(const QString &roomid, const QPixmap &img);
@ -238,6 +247,8 @@ private:
// Global user settings. // Global user settings.
QSharedPointer<UserSettings> userSettings_; QSharedPointer<UserSettings> userSettings_;
NotificationsManager notificationsManager;
}; };
template<class Collection> template<class Collection>

View file

@ -1,12 +1,55 @@
#pragma once #pragma once
#include <QImage> #include <QImage>
#include <QObject>
#include <QString> #include <QString>
class NotificationsManager #if defined(Q_OS_LINUX)
#include <QtDBus/QDBusArgument>
#include <QtDBus/QDBusInterface>
#endif
struct roomEventId
{ {
public: QString roomId;
static void postNotification(const QString &room, QString eventId;
const QString &user,
const QString &message);
}; };
class NotificationsManager : public QObject
{
Q_OBJECT
public:
NotificationsManager(QObject *parent = nullptr);
void postNotification(const QString &roomId,
const QString &eventId,
const QString &roomName,
const QString &senderName,
const QString &text,
const QImage &icon);
signals:
void notificationClicked(const QString roomId, const QString eventId);
#if defined(Q_OS_LINUX)
private:
QDBusInterface dbus;
uint showNotification(const QString summary, const QString text, const QImage image);
// notification ID to (room ID, event ID)
QMap<uint, roomEventId> notificationIds;
#endif
// these slots are platform specific (D-Bus only)
// but Qt slot declarations can not be inside an ifdef!
private slots:
void actionInvoked(uint id, QString action);
void notificationClosed(uint id, uint reason);
};
#if defined(Q_OS_LINUX)
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image);
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &);
#endif

View file

@ -56,6 +56,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, isConnected_(true) , isConnected_(true)
, userSettings_{userSettings} , userSettings_{userSettings}
, notificationsManager(this)
{ {
setObjectName("chatPage"); setObjectName("chatPage");
@ -541,6 +542,15 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
room_list_->setRoomFilter(communities_[communityId]->getRoomList()); room_list_->setRoomFilter(communities_[communityId]->getRoomList());
}); });
connect(&notificationsManager,
&NotificationsManager::notificationClicked,
this,
[this](const QString &roomid, const QString &eventid) {
Q_UNUSED(eventid)
room_list_->highlightSelectedRoom(roomid);
activateWindow();
});
setGroupViewState(userSettings_->isGroupViewEnabled()); setGroupViewState(userSettings_->isGroupViewEnabled());
connect(userSettings_.data(), connect(userSettings_.data(),
@ -998,11 +1008,14 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
if (isRoomActive(room_id)) if (isRoomActive(room_id))
continue; continue;
NotificationsManager::postNotification( notificationsManager.postNotification(
room_id,
QString::fromStdString(event_id),
QString::fromStdString( QString::fromStdString(
cache::client()->singleRoomInfo(item.room_id).name), cache::client()->singleRoomInfo(item.room_id).name),
Cache::displayName(room_id, user_id), Cache::displayName(room_id, user_id),
utils::event_body(item.event)); utils::event_body(item.event),
cache::client()->getRoomAvatar(room_id));
} }
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
nhlog::db()->warn("error while sending desktop notification: {}", e.what()); nhlog::db()->warn("error while sending desktop notification: {}", e.what());

View file

@ -1,7 +1,158 @@
#include "notifications/Manager.h" #include "notifications/Manager.h"
void #include <QImage>
NotificationsManager::postNotification(const QString &, const QString &, const QString &) #include <QDebug>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusMetaType>
#include <QtDBus/QDBusConnection>
NotificationsManager::NotificationsManager(QObject *parent) :
QObject(parent),
dbus(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
QDBusConnection::sessionBus(),
this)
{ {
// TODO: To be implemented qDBusRegisterMetaType<QImage>();
//connectSlot("ActionInvoked", SLOT(actionInvoked(uint, QString)));
//connectSlot("NotificationClosed", SLOT(notificationClosed(uint, uint)));
QDBusConnection::sessionBus().connect(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"ActionInvoked",
this,
SLOT(actionInvoked(uint, QString)));
QDBusConnection::sessionBus().connect(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationClosed",
this,
SLOT(notificationClosed(uint, uint)));
}
void
NotificationsManager::postNotification(const QString &roomid,
const QString &eventid,
const QString &roomname,
const QString &sender,
const QString &text,
const QImage &icon)
{
uint id = showNotification(roomname, sender+": "+text, icon);
notificationIds[id] = roomEventId{roomid,eventid};
}
/**
* 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
*/
uint
NotificationsManager::showNotification(const QString summary, const QString text, const QImage image)
{
QVariantMap hints;
hints["image_data"] = image;
QList<QVariant> argumentList;
argumentList << "nheko"; //app_name
argumentList << (uint)0; // replace_id
argumentList << ""; // app_icon
argumentList << summary; // summary
argumentList << text; // body
argumentList << (QStringList("default")<<"reply"); // actions
argumentList << hints; // hints
argumentList << (int)0; // timeout in ms
static QDBusInterface notifyApp(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications");
QDBusMessage reply = notifyApp.callWithArgumentList(
QDBus::AutoDetect,
"Notify",
argumentList);
if(reply.type() == QDBusMessage::ErrorMessage) {
qDebug() << "D-Bus Error:" << reply.errorMessage();
return 0;
} else {
return reply.arguments().first().toUInt();
}
return true;
}
void
NotificationsManager::actionInvoked(uint id, QString action)
{
if (action == "default" && notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id];
emit notificationClicked(idEntry.roomId, idEntry.eventId);
}
}
void
NotificationsManager::notificationClosed(uint id, uint reason)
{
Q_UNUSED(reason);
notificationIds.remove(id);
}
/**
* Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify
*
* This function is from the Clementine project (see
* http://www.clementine-player.org) and licensed under the GNU General Public
* License, version 3 or later.
*
* Copyright 2010, David Sansome <me@davidsansome.com>
*/
QDBusArgument& operator<<(QDBusArgument& arg, const QImage& image) {
if(image.isNull()) {
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
// ABGR -> ARGB
QImage i = scaled.rgbSwapped();
#else
// ABGR -> GBAR
QImage i(scaled.size(), scaled.format());
for (int y = 0; y < i.height(); ++y) {
QRgb* p = (QRgb*) scaled.scanLine(y);
QRgb* q = (QRgb*) i.scanLine(y);
QRgb* end = p + scaled.width();
while (p < end) {
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
p++;
q++;
}
}
#endif
arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
arg << i.depth() / channels;
arg << channels;
arg << QByteArray(reinterpret_cast<const char*>(i.bits()), i.byteCount());
arg.endStructure();
return arg;
}
const QDBusArgument& operator>>(const QDBusArgument& arg, QImage&) {
// This is needed to link but shouldn't be called.
Q_ASSERT(0);
return arg;
} }

View file

@ -7,16 +7,42 @@
- (void)set_identityImage:(NSImage *)image; - (void)set_identityImage:(NSImage *)image;
@end @end
void NotificationsManager::NotificationsManager(QObject *parent): QObject(parent)
NotificationsManager::postNotification(const QString &roomName, const QString &userName, const QString &message)
{ {
}
void
NotificationsManager::postNotification(
const QString &roomId,
const QString &eventId,
const QString &roomName,
const QString &senderName,
const QString &text,
const QImage &icon)
{
Q_UNUSED(roomId);
Q_UNUSED(eventId);
Q_UNUSED(icon);
NSUserNotification * notif = [[NSUserNotification alloc] init]; NSUserNotification * notif = [[NSUserNotification alloc] init];
notif.title = roomName.toNSString(); notif.title = roomName.toNSString();
notif.subtitle = QString("%1 sent a message").arg(userName).toNSString(); notif.subtitle = QString("%1 sent a message").arg(senderName).toNSString();
notif.informativeText = message.toNSString(); notif.informativeText = text.toNSString();
notif.soundName = NSUserNotificationDefaultSoundName; notif.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif]; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif];
[notif autorelease]; [notif autorelease];
} }
//unused
void
NotificationsManager::actionInvoked(uint, QString)
{
}
void
NotificationsManager::notificationClosed(uint, uint)
{
}

View file

@ -27,15 +27,25 @@ init()
} }
} }
NotificationsManager::NotificationsManager(QObject *parent): QObject(parent)
{
}
void void
NotificationsManager::postNotification(const QString &room, const QString &user, const QString &msg) NotificationsManager::postNotification(const QString &, //roomid
const QString &, //eventid
const QString &roomname,
const QString &sender,
const QString &text,
const QImage &) //icon
{ {
if (!isInitialized) if (!isInitialized)
init(); init();
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02); auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
if (room != user) if (roomname != sender)
templ.setTextField(QString("%1 - %2").arg(user).arg(room).toStdWString(), templ.setTextField(QString("%1 - %2").arg(sender).arg(roomname).toStdWString(),
WinToastTemplate::FirstLine); WinToastTemplate::FirstLine);
else else
templ.setTextField(QString("%1").arg(user).toStdWString(), templ.setTextField(QString("%1").arg(user).toStdWString(),
@ -46,3 +56,14 @@ NotificationsManager::postNotification(const QString &room, const QString &user,
WinToast::instance()->showToast(templ, new CustomHandler()); WinToast::instance()->showToast(templ, new CustomHandler());
} }
//unused
void
NotificationsManager::actionInvoked(uint, QString)
{
}
void
NotificationsManager::notificationClosed(uint, uint)
{
}