kdelibs/kdeui/notifications/knotification.cpp

475 lines
15 KiB
C++
Raw Normal View History

/* This file is part of the KDE libraries
Copyright (C) 2023 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
2014-11-13 01:04:59 +02:00
*/
#include "knotification.h"
#include "kglobal.h"
#include "kcomponentdata.h"
#include "kconfig.h"
#include "kconfiggroup.h"
#include "kstandarddirs.h"
#include "kwindowsystem.h"
#include "kdbusconnectionpool.h"
#include "kiconloader.h"
#include "kpassivepopup.h"
#include "kdirwatch.h"
#include "kdebug.h"
#include <QMutex>
#include <QDBusConnectionInterface>
#include <QDBusInterface>
#include <QDBusReply>
2014-11-13 01:04:59 +02:00
#include <QTimer>
// for reference:
// https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html
// see kdebug.areas
static const int s_knotificationarea = 299;
static const QString s_notifications = QString::fromLatin1("org.freedesktop.Notifications");
class KNotificationManager : public QObject
2014-11-13 01:04:59 +02:00
{
Q_OBJECT
public:
KNotificationManager();
~KNotificationManager();
2014-11-13 01:04:59 +02:00
void send(KNotification *notification, const bool persistent);
void close(KNotification *notification);
private Q_SLOTS:
void slotNotificationClosed(uint eventid, uint reason);
void slotActionInvoked(uint eventid, const QString &action);
void slotDirty(const QString &path);
private:
QMutex m_mutex;
KConfig *m_config;
KDirWatch m_configwatch;
QDBusInterface* m_notificationsiface;
QDBusInterface* m_kaudioplayeriface;
QMap<KNotification*,uint> m_notifications;
2014-11-13 01:04:59 +02:00
};
K_GLOBAL_STATIC(KNotificationManager, kNotificationManager);
2014-11-13 01:04:59 +02:00
KNotificationManager::KNotificationManager()
: m_config(nullptr),
m_configwatch(this),
m_notificationsiface(nullptr),
m_kaudioplayeriface(nullptr)
2014-11-13 01:04:59 +02:00
{
// NOTE: the default poll interval of QFileSystemWatcher is 1sec
m_configwatch.setInterval(5000);
const QString knotificationrc = KGlobal::dirs()->saveLocation("config") + QLatin1String("knotificationrc");
// qDebug() << Q_FUNC_INFO << knotificationrc;
Q_ASSERT(!knotificationrc.isEmpty());
m_configwatch.addFile(knotificationrc);
const QStringList configdirs = KGlobal::dirs()->resourceDirs("config");
foreach (const QString &configdir, configdirs) {
const QString notificationdir = configdir + QLatin1String("notifications/");
// qDebug() << Q_FUNC_INFO << notificationdir;
m_configwatch.addDir(notificationdir);
}
slotDirty(QString());
connect(&m_configwatch, SIGNAL(dirty(QString)), this, SLOT(slotDirty(QString)));
2014-11-13 01:04:59 +02:00
}
KNotificationManager::~KNotificationManager()
{
delete m_config;
}
void KNotificationManager::send(KNotification *notification, const bool persistent)
2014-11-13 01:04:59 +02:00
{
Q_ASSERT(m_config);
QMutexLocker locker(&m_mutex);
const QString eventid = notification->eventID();
const QStringList spliteventid = eventid.split(QLatin1Char('/'));
// qDebug() << Q_FUNC_INFO << spliteventid;
if (spliteventid.size() != 2) {
kWarning(s_knotificationarea) << "invalid notification ID" << eventid;
return;
}
KConfigGroup globalgroup(m_config, spliteventid.at(0));
const QString globalcomment = globalgroup.readEntry("Comment");
KConfigGroup eventgroup(m_config, eventid);
QString eventtitle = notification->title();
if (eventtitle.isEmpty()) {
eventtitle = eventgroup.readEntry("Comment");
}
if (eventtitle.isEmpty()) {
eventtitle = globalcomment;
}
2014-11-13 01:04:59 +02:00
QString eventtext = notification->text();
if (eventtext.isEmpty()) {
eventtext = eventgroup.readEntry("Name");
}
if (eventtext.isEmpty()) {
eventtext = globalgroup.readEntry("Name");
}
2014-11-13 01:04:59 +02:00
QString eventicon = notification->icon();
if (eventicon.isEmpty()) {
eventicon = eventgroup.readEntry("IconName");
}
if (eventicon.isEmpty()) {
eventicon = globalgroup.readEntry("IconName");
}
2014-11-13 01:04:59 +02:00
QStringList eventactions = eventgroup.readEntry("Actions", QStringList());
if (eventactions.isEmpty()) {
eventactions = globalgroup.readEntry("Actions", QStringList());
}
// qDebug() << Q_FUNC_INFO << eventactions << notification->actions();
if (eventactions.contains(QString::fromLatin1("Popup"))) {
if (!m_notificationsiface
&& KDBusConnectionPool::isServiceRegistered(s_notifications, QDBusConnection::sessionBus())) {
m_notificationsiface = new QDBusInterface(
s_notifications, "/org/freedesktop/Notifications", s_notifications,
QDBusConnection::sessionBus(), this
);
connect(m_notificationsiface, SIGNAL(NotificationClosed(uint,uint)), this, SLOT(slotNotificationClosed(uint,uint)));
connect(m_notificationsiface, SIGNAL(ActionInvoked(uint,QString)), this, SLOT(slotActionInvoked(uint,QString)));
}
const int eventtimeout = (persistent ? 0 : -1);
if (!m_notificationsiface || !m_notificationsiface->isValid()) {
kWarning(s_knotificationarea) << "notifications interface is not valid";
const QPixmap eventpixmap = KIconLoader::global()->loadIcon(eventicon, KIconLoader::Small);
KPassivePopup* kpassivepopup = new KPassivePopup(notification->widget());
kpassivepopup->setTimeout(eventtimeout);
kpassivepopup->setView(eventtitle, eventtext, eventpixmap);
kpassivepopup->setAutoDelete(true);
// NOTE: KPassivePopup positions itself depending on the windows
kpassivepopup->show();
} else {
const uint eventid = m_notifications.value(notification, 0);
// NOTE: there has to be id for each action, starting from 1
int actionscounter = 1;
QStringList eventactions;
foreach (const QString &eventaction, notification->actions()) {
eventactions.append(QString::number(actionscounter));
eventactions.append(eventaction);
actionscounter++;
}
QVariantMap eventhints;
QString eventapp = globalcomment;
if (eventapp.isEmpty()) {
eventapp = KGlobal::mainComponent().componentName();
}
// NOTE: has to be set to be configurable via plasma notifications applet
eventhints.insert("x-kde-appname", spliteventid.at(0));
QDBusReply<uint> notifyreply = m_notificationsiface->call(
QString::fromLatin1("Notify"),
eventapp,
eventid,
eventicon,
eventtitle,
eventtext,
eventactions,
eventhints,
eventtimeout
);
if (!notifyreply.isValid()) {
kWarning(s_knotificationarea) << "invalid notify reply" << notifyreply.error().message();
} else {
m_notifications.insert(notification, notifyreply.value());
}
}
}
2014-11-13 01:04:59 +02:00
if (eventactions.contains(QString::fromLatin1("Sound"))) {
QString eventsound = eventgroup.readEntry("Sound");
if (eventsound.isEmpty()) {
eventsound = globalgroup.readEntry("Sound");
}
const QStringList eventsoundfiles = KGlobal::dirs()->findAllResources("sound", eventsound, KStandardDirs::Recursive);
if (eventsoundfiles.isEmpty()) {
kWarning(s_knotificationarea) << "sound not found" << eventsound;
} else {
kDebug(s_knotificationarea) << "playing notification sound" << eventsound;
if (!m_kaudioplayeriface) {
m_kaudioplayeriface = new QDBusInterface(
"org.kde.kded", "/modules/kaudioplayer", "org.kde.kaudioplayer",
QDBusConnection::sessionBus(), this
);
}
// the sound player is configurable and is used by the bball plasma applet for example
QDBusReply<void> playreply = m_kaudioplayeriface->call(QString::fromLatin1("play"), eventsoundfiles.first());
if (!playreply.isValid()) {
kWarning(s_knotificationarea) << "invalid play reply" << playreply.error().message();
}
}
}
2014-11-13 01:04:59 +02:00
if (eventactions.contains(QString::fromLatin1("Taskbar"))) {
const QWidget* eventwidget = notification->widget();
if (!eventwidget) {
kWarning(s_knotificationarea) << "taskbar event with no widget set" << eventid;
} else {
const WId eventwidgetid = eventwidget->winId();
kDebug(s_knotificationarea) << "marking notification task" << eventid << eventwidgetid;
KWindowSystem::demandAttention(eventwidgetid);
}
}
2014-11-13 01:04:59 +02:00
}
void KNotificationManager::close(KNotification *notification)
2014-11-13 01:04:59 +02:00
{
QMutexLocker locker(&m_mutex);
QMutableMapIterator<KNotification*,uint> iter(m_notifications);
while (iter.hasNext()) {
iter.next();
if (iter.key() == notification) {
iter.remove();
QDBusReply<void> closereply = m_notificationsiface->call(
QString::fromLatin1("CloseNotification"),
iter.value()
);
if (!closereply.isValid()) {
kWarning(s_knotificationarea) << "invalid close reply" << closereply.error().message();
}
}
}
2014-11-13 01:04:59 +02:00
}
void KNotificationManager::slotNotificationClosed(uint eventid, uint reason)
2014-11-13 01:04:59 +02:00
{
kDebug(s_knotificationarea) << "closing notifications due to interface" << reason;
QMutableMapIterator<KNotification*,uint> iter(m_notifications);
while (iter.hasNext()) {
iter.next();
if (iter.value() == eventid) {
KNotification* notification = iter.key();
notification->close();
}
2014-11-13 01:04:59 +02:00
}
}
void KNotificationManager::slotActionInvoked(uint eventid, const QString &action)
2014-11-13 01:04:59 +02:00
{
kDebug(s_knotificationarea) << "notification action invoked" << action;
QMutableMapIterator<KNotification*,uint> iter(m_notifications);
while (iter.hasNext()) {
iter.next();
if (iter.value() == eventid) {
KNotification* notification = iter.key();
notification->activate(action.toUInt());
}
}
2014-11-13 01:04:59 +02:00
}
void KNotificationManager::slotDirty(const QString &path)
{
kDebug(s_knotificationarea) << "dirty" << path;
QMutexLocker locker(&m_mutex);
delete m_config;
m_config = new KConfig("knotificationrc", KConfig::NoGlobals);
const QStringList notifyconfigs = KGlobal::dirs()->findAllResources("config", "notifications/*.notifyrc");
if (!notifyconfigs.isEmpty()) {
m_config->addConfigSources(notifyconfigs);
}
// qDebug() << Q_FUNC_INFO << notifyconfigs;
}
class KNotificationPrivate
2014-11-13 01:04:59 +02:00
{
public:
KNotificationPrivate();
QString eventid;
QString title;
QString text;
QString icon;
QWidget *widget;
QStringList actions;
KNotification::NotificationFlags flags;
};
2014-11-13 01:04:59 +02:00
KNotificationPrivate::KNotificationPrivate()
: widget(nullptr)
2014-11-13 01:04:59 +02:00
{
}
KNotification::KNotification(QObject *parent)
: QObject(parent),
d(new KNotificationPrivate())
2014-11-13 01:04:59 +02:00
{
}
KNotification::~KNotification()
2014-11-13 01:04:59 +02:00
{
close();
delete d;
2014-11-13 01:04:59 +02:00
}
QString KNotification::eventID() const
2014-11-13 01:04:59 +02:00
{
return d->eventid;
2014-11-13 01:04:59 +02:00
}
void KNotification::setEventID(const QString &eventid)
2014-11-13 01:04:59 +02:00
{
d->eventid = eventid;
2014-11-13 01:04:59 +02:00
}
QString KNotification::title() const
2014-11-13 01:04:59 +02:00
{
return d->title;
2014-11-13 01:04:59 +02:00
}
void KNotification::setTitle(const QString &title)
2014-11-13 01:04:59 +02:00
{
d->title = title;
2014-11-13 01:04:59 +02:00
}
QString KNotification::text() const
2014-11-13 01:04:59 +02:00
{
return d->text;
2014-11-13 01:04:59 +02:00
}
void KNotification::setText(const QString &text)
2014-11-13 01:04:59 +02:00
{
d->text = text;
2014-11-13 01:04:59 +02:00
}
QString KNotification::icon() const
2014-11-13 01:04:59 +02:00
{
return d->icon;
2014-11-13 01:04:59 +02:00
}
void KNotification::setIcon(const QString &icon)
2014-11-13 01:04:59 +02:00
{
d->icon = icon;
2014-11-13 01:04:59 +02:00
}
QWidget* KNotification::widget() const
2014-11-13 01:04:59 +02:00
{
return d->widget;
2014-11-13 01:04:59 +02:00
}
void KNotification::setWidget(QWidget *widget)
2014-11-13 01:04:59 +02:00
{
d->widget = widget;
setParent(widget);
if (widget && (d->flags & KNotification::CloseWhenWidgetActivated)) {
widget->installEventFilter(this);
}
2014-11-13 01:04:59 +02:00
}
QStringList KNotification::actions() const
2014-11-13 01:04:59 +02:00
{
return d->actions;
2014-11-13 01:04:59 +02:00
}
void KNotification::setActions(const QStringList &actions)
2014-11-13 01:04:59 +02:00
{
d->actions = actions;
2014-11-13 01:04:59 +02:00
}
KNotification::NotificationFlags KNotification::flags() const
2014-11-13 01:04:59 +02:00
{
return d->flags;
2014-11-13 01:04:59 +02:00
}
void KNotification::setFlags(const NotificationFlags &flags)
2014-11-13 01:04:59 +02:00
{
d->flags = flags;
2014-11-13 01:04:59 +02:00
}
void KNotification::send()
{
kDebug(s_knotificationarea) << "sending notification" << d->eventid;
const bool persistent = (flags() & KNotification::Persistent);
kNotificationManager->send(this, persistent);
if (!persistent) {
QTimer::singleShot(500, this, SLOT(close()));
}
}
2014-11-13 01:04:59 +02:00
void KNotification::activate(unsigned int action)
2015-09-22 10:34:04 +00:00
{
kDebug(s_knotificationarea) << "activating notification action" << d->eventid << action;
switch (action) {
case 1: {
emit action1Activated();
2015-09-22 10:34:04 +00:00
break;
}
case 2: {
emit action2Activated();
2015-09-22 10:34:04 +00:00
break;
}
case 3: {
emit action3Activated();
2015-09-22 10:34:04 +00:00
break;
}
default: {
kWarning(s_knotificationarea) << "invalid action" << action;
2015-09-22 10:34:04 +00:00
break;
}
2015-09-22 10:34:04 +00:00
}
close();
2014-11-13 01:04:59 +02:00
}
void KNotification::close()
2014-11-13 01:04:59 +02:00
{
kDebug(s_knotificationarea) << "closing notification" << d->eventid;
kNotificationManager->close(this);
emit closed();
deleteLater();
2014-11-13 01:04:59 +02:00
}
bool KNotification::eventFilter(QObject *watched, QEvent *event)
2014-11-13 01:04:59 +02:00
{
if (watched == d->widget) {
if (event->type() == QEvent::WindowActivate
&& d->flags & KNotification::CloseWhenWidgetActivated) {
kDebug(s_knotificationarea) << "closing due to widget activation" << d->eventid;
QTimer::singleShot(500, this, SLOT(close()));
}
2015-09-22 10:34:04 +00:00
}
return false;
2014-11-13 01:04:59 +02:00
}
void KNotification::event(const QString &eventid, const QString &title, const QString &text,
const QString &icon, QWidget *widget, const NotificationFlags &flags)
2014-11-13 01:04:59 +02:00
{
KNotification* knotification = new KNotification(widget);
knotification->setEventID(eventid);
knotification->setTitle(title);
knotification->setText(text);
knotification->setIcon(icon);
knotification->setWidget(widget);
knotification->setFlags(flags);
QTimer::singleShot(0, knotification, SLOT(send()));
2014-11-13 01:04:59 +02:00
}
void KNotification::beep(const QString &reason, QWidget *widget)
2014-11-13 01:04:59 +02:00
{
event(
QString::fromLatin1("kde/beep"), QString(), reason, QString(), widget,
KNotification::CloseOnTimeout
);
2014-11-13 01:04:59 +02:00
}
#include "moc_knotification.cpp"
#include "knotification.moc"