Commit f072a0c6 authored by Holger Kaelberer's avatar Holger Kaelberer
Browse files

notifications: allow to configure how to sync desktop notifications

Added kcm ui with global and app-specific config options.

Global options affect all notifications:

- Persistent only? --> Sync only notifications with timeout == 0?
- Include body? --> Add body string to summary when syncing?
- Minimum urgency

Per-application options affect notifications sent by a specific
application:

- Sync it at all?
- If yes, allow to define a "blacklist" pattern (QRegularExpression)
  to define which notifications should *not* be synced.

Applications are maintained in the per-device config and added when
seen the first time by the notification listener. From that moment on
they can be configured in the kcm ui.
parent 2a74eb68
......@@ -62,6 +62,24 @@ QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& default
return d->mConfig->value(key, defaultValue);
}
QVariantList KdeConnectPluginConfig::getList(const QString& key,
const QVariantList& defaultValue)
{
QVariantList list;
d->mConfig->sync(); // note: need sync() to get recent changes signalled from other process
int size = d->mConfig->beginReadArray(key);
if (size < 1) {
d->mConfig->endArray();
return defaultValue;
}
for (int i = 0; i < size; ++i) {
d->mConfig->setArrayIndex(i);
list << d->mConfig->value("value");
}
d->mConfig->endArray();
return list;
}
void KdeConnectPluginConfig::set(const QString& key, const QVariant& value)
{
d->mConfig->setValue(key, value);
......@@ -69,6 +87,18 @@ void KdeConnectPluginConfig::set(const QString& key, const QVariant& value)
QDBusConnection::sessionBus().send(d->signal);
}
void KdeConnectPluginConfig::setList(const QString& key, const QVariantList& list)
{
d->mConfig->beginWriteArray(key);
for (int i = 0; i < list.size(); ++i) {
d->mConfig->setArrayIndex(i);
d->mConfig->setValue("value", list.at(i));
}
d->mConfig->endArray();
d->mConfig->sync();
QDBusConnection::sessionBus().send(d->signal);
}
void KdeConnectPluginConfig::slotConfigChanged()
{
Q_EMIT configChanged();
......
......@@ -50,6 +50,12 @@ public:
*/
void set(const QString& key, const QVariant& value);
/**
* Store a list of values in this config object under the array name
* specified in key.
*/
void setList(const QString& key, const QVariantList& list);
/**
* Read a key-value pair from this config object
*/
......@@ -62,6 +68,8 @@ public:
return get(key, QVariant(defaultValue)).template value<T>(); //Important note: Awesome template syntax is awesome
}
QVariantList getList(const QString& key, const QVariantList& defaultValue = {});
private Q_SLOTS:
void slotConfigChanged();
......
find_package(KF5 REQUIRED COMPONENTS Notifications)
find_package(KF5 REQUIRED COMPONENTS Notifications KCMUtils I18n)
set(kdeconnect_notifications_SRCS
notification.cpp
notificationsplugin.cpp
notificationsdbusinterface.cpp
notificationslistener.cpp
notifyingapplication.cpp
)
kdeconnect_add_plugin(kdeconnect_notifications JSON kdeconnect_notifications.json SOURCES ${kdeconnect_notifications_SRCS})
......@@ -13,4 +14,26 @@ target_link_libraries(kdeconnect_notifications
kdeconnectcore
Qt5::DBus
KF5::Notifications
KF5::I18n
)
#######################################
# Config
set( kdeconnect_notifications_config_SRCS
notifications_config.cpp
notifyingapplication.cpp
notifyingapplicationmodel.cpp
)
ki18n_wrap_ui( kdeconnect_notifications_config_SRCS notifications_config.ui )
add_library(kdeconnect_notifications_config MODULE ${kdeconnect_notifications_config_SRCS} )
target_link_libraries( kdeconnect_notifications_config
kdeconnectcore
kdeconnectpluginkcm
KF5::I18n
KF5::KCMUtils
)
install( TARGETS kdeconnect_notifications_config DESTINATION ${PLUGIN_INSTALL_DIR} )
install( FILES kdeconnect_notifications_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=KCModule
X-KDE-Library=kdeconnect_notifications_config
X-KDE-ParentComponents=kdeconnect_notifications
Name=Notification synchronization plugin settings
Categories=Qt;KDE;X-KDE-settings-kdeconnect;
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "notifications_config.h"
#include "ui_notifications_config.h"
#include "notifyingapplicationmodel.h"
#include <KCModule>
#include <KPluginFactory>
K_PLUGIN_FACTORY(NotificationsConfigFactory, registerPlugin<NotificationsConfig>();)
NotificationsConfig::NotificationsConfig(QWidget *parent, const QVariantList& args)
: KdeConnectPluginKcm(parent, args, "kdeconnect_notifications_config")
, m_ui(new Ui::NotificationsConfigUi())
, appModel(new NotifyingApplicationModel)
{
qRegisterMetaTypeStreamOperators<NotifyingApplication>("NotifyingApplication");
m_ui->setupUi(this);
m_ui->appList->setIconSize(QSize(32,32));
m_ui->appList->setModel(appModel);
m_ui->appList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::QHeaderView::Fixed);
m_ui->appList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::QHeaderView::Stretch);
m_ui->appList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::QHeaderView::Stretch);
for (int i = 0; i < 3; i++)
m_ui->appList->resizeColumnToContents(i);
connect(m_ui->appList->horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
m_ui->appList, SLOT(sortByColumn(int)));
connect(m_ui->check_persistent, SIGNAL(toggled(bool)), this, SLOT(changed()));
connect(m_ui->spin_urgency, SIGNAL(editingFinished()), this, SLOT(changed()));
connect(m_ui->check_body, SIGNAL(toggled(bool)), this, SLOT(changed()));
connect(appModel, SIGNAL(applicationsChanged()), this, SLOT(changed()));
connect(config(), SIGNAL(configChanged()), this, SLOT(loadApplications()));
}
NotificationsConfig::~NotificationsConfig()
{
delete m_ui;
}
void NotificationsConfig::defaults()
{
KCModule::defaults();
m_ui->check_persistent->setChecked(false);
m_ui->spin_urgency->setValue(0);
Q_EMIT changed(true);
}
void NotificationsConfig::loadApplications()
{
appModel->clearApplications();
QVariantList list = config()->getList("applications");
for (const auto& a: list) {
NotifyingApplication app = a.value<NotifyingApplication>();
if (!appModel->containsApp(app.name)) {
appModel->appendApp(app);
}
}
}
void NotificationsConfig::load()
{
KCModule::load();
bool persistent = config()->get("generalPersistent", false);
m_ui->check_persistent->setChecked(persistent);
bool body = config()->get("generalIncludeBody", true);
m_ui->check_body->setChecked(body);
int urgency = config()->get("generalUrgency", 0);
m_ui->spin_urgency->setValue(urgency);
loadApplications();
Q_EMIT changed(false);
}
void NotificationsConfig::save()
{
config()->set("generalPersistent", m_ui->check_persistent->isChecked());
config()->set("generalIncludeBody", m_ui->check_body->isChecked());
config()->set("generalUrgency", m_ui->spin_urgency->value());
QVariantList list;
for (const auto& a: appModel->apps())
list << QVariant::fromValue<NotifyingApplication>(a);
config()->setList("applications", list);
KCModule::save();
Q_EMIT changed(false);
}
#include "notifications_config.moc"
/**
* Copyright 2015 Holger Kaelberer <holger.k@elberer.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NOTIFICATIONS_CONFIG_H
#define NOTIFICATIONS_CONFIG_H
#include "kcmplugin/kdeconnectpluginkcm.h"
namespace Ui {
class NotificationsConfigUi;
}
class NotifyingApplicationModel;
class NotificationsConfig
: public KdeConnectPluginKcm
{
Q_OBJECT
public:
NotificationsConfig(QWidget *parent, const QVariantList&);
virtual ~NotificationsConfig();
public Q_SLOTS:
virtual void save() override;
virtual void load() override;
virtual void defaults() override;
private Q_SLOTS:
void loadApplications();
private:
Ui::NotificationsConfigUi* m_ui;
NotifyingApplicationModel* appModel;
};
#endif
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NotificationsConfigUi</class>
<widget class="QWidget" name="NotificationsConfigUi">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>326</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Pause music plugin</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>General</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="check_persistent">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Synchronize only notifications with a timeout value of 0?</string>
</property>
<property name="text">
<string>Persistent notifications only</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_body">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Append the notification body to the summary when synchronizing notifications?</string>
</property>
<property name="text">
<string>Include body</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="horizontalWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="spin_urgency">
<property name="maximumSize">
<size>
<width>40</width>
<height>32</height>
</size>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Minimum urgency level of the notifications&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Synchronize only notifications with the given urgency level.</string>
</property>
<property name="text">
<string>Minimum urgency level</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="toolTip">
<string/>
</property>
<property name="title">
<string>Applications</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QTableView" name="appList">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>20</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
......@@ -30,6 +30,7 @@
#include "notificationsplugin.h"
#include "notification_debug.h"
#include "notificationsdbusinterface.h"
#include "notifyingapplication.h"
NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin,
NotificationsDbusInterface* aDbusInterface)
......@@ -37,6 +38,8 @@ NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin,
mPlugin(aPlugin),
dbusInterface(aDbusInterface)
{
qRegisterMetaTypeStreamOperators<NotifyingApplication>("NotifyingApplication");
bool ret = QDBusConnection::sessionBus()
.registerObject("/org/freedesktop/Notifications",
this,
......@@ -55,6 +58,10 @@ NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin,
"org.freedesktop.DBus");
iface.call("AddMatch",
"interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'");
loadApplications();
connect(mPlugin->config(), SIGNAL(configChanged()), this, SLOT(loadApplications()));
}
NotificationsListener::~NotificationsListener()
......@@ -67,6 +74,18 @@ NotificationsListener::~NotificationsListener()
QDBusConnection::sessionBus().unregisterObject("/org/freedesktop/Notifications");
}
void NotificationsListener::loadApplications()
{
applications.clear();
QVariantList list = mPlugin->config()->getList("applications");
for (const auto& a: list) {
NotifyingApplication app = a.value<NotifyingApplication>();
if (!applications.contains(app.name))
applications.insert(app.name, app);
}
//qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Loaded" << applications.size() << " applications";
}
uint NotificationsListener::Notify(const QString &appName, uint replacesId,
const QString &appIcon,
const QString &summary, const QString &body,
......@@ -74,20 +93,60 @@ uint NotificationsListener::Notify(const QString &appName, uint replacesId,
const QVariantMap &hints, int timeout)
{
static int id = 0;
qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout;
Q_UNUSED(hints);
Q_UNUSED(actions);
Q_UNUSED(appIcon);
Q_UNUSED(body);
//qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout;
// skip our own notifications
if (appName == QLatin1String("KDE Connect"))
return 0;
NotifyingApplication app;
if (!applications.contains(appName)) {
// new application -> add to config
app.name = appName;
app.icon = appIcon;
app.active = true;
app.blacklistExpression = QRegularExpression();
applications.insert(app.name, app);
// update config:
QVariantList list;
for (const auto& a: applications.values())
list << QVariant::fromValue<NotifyingApplication>(a);
mPlugin->config()->setList("applications", list);
//qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Added new application to config:" << app;
} else
app = applications.value(appName);
if (!app.active)
return 0;
if (timeout > 0 && mPlugin->config()->get("generalPersistent", false))
return 0;
int urgency = -1;
bool ok;
if (hints.contains("urgency"))
urgency = hints["urgency"].toInt(&ok);
if (!ok)
urgency = -1;
if (urgency > -1 && urgency < mPlugin->config()->get<int>("generalUrgency", 0))
return 0;
QString ticker = summary;
if (!body.isEmpty() && mPlugin->config()->get("generalIncludeBody", true))
ticker += QLatin1String(": ") + body;
if (app.blacklistExpression.isValid() &&
!app.blacklistExpression.pattern().isEmpty() &&
app.blacklistExpression.match(ticker).hasMatch())
return 0;
//qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Sending notification from" << appName << ":" <<ticker;
NetworkPackage np(PACKAGE_TYPE_NOTIFICATION);
np.set("id", QString::number(replacesId > 0 ? replacesId : ++id));
np.set("appName", appName);
np.set("ticker", summary);
np.set("ticker", ticker);
np.set("isClearable", timeout == 0); // KNotifications are persistent if
// timeout == 0, for other notifications