From 247176e61399f14f6d52638d74d5a2a2579c1f55 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 24 Apr 2015 23:00:56 +0200 Subject: added notificationmonitor, notification --- libwatchfish.pro | 14 +++ notification.cpp | 7 ++ notification.h | 51 ++++++++ notificationmonitor.cpp | 322 ++++++++++++++++++++++++++++++++++++++++++++++++ notificationmonitor.h | 34 +++++ watchfish.pro | 4 - 6 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 libwatchfish.pro create mode 100644 notification.cpp create mode 100644 notification.h create mode 100644 notificationmonitor.cpp create mode 100644 notificationmonitor.h delete mode 100644 watchfish.pro diff --git a/libwatchfish.pro b/libwatchfish.pro new file mode 100644 index 0000000..7967685 --- /dev/null +++ b/libwatchfish.pro @@ -0,0 +1,14 @@ +TARGET = watchfish + +TEMPLATE = lib +CONFIG += staticlib + +CONFIG += c++11 link_pkgconfig +PKGCONFIG += dbus-1 +INCLUDEPATH += /usr/include/dbus-1.0 + +HEADERS = notificationmonitor.h \ + notification.h +SOURCES = notificationmonitor.cpp \ + notification.cpp + diff --git a/notification.cpp b/notification.cpp new file mode 100644 index 0000000..09f61fb --- /dev/null +++ b/notification.cpp @@ -0,0 +1,7 @@ +#include "notification.h" + +using namespace watchfish; + +Notification::Notification(QObject *parent) : QObject(parent) +{ +} diff --git a/notification.h b/notification.h new file mode 100644 index 0000000..46b6754 --- /dev/null +++ b/notification.h @@ -0,0 +1,51 @@ +#ifndef WATCHFISH_NOTIFICATION_H +#define WATCHFISH_NOTIFICATION_H + +#include +#include + +namespace watchfish +{ + +class Notification : public QObject +{ + Q_OBJECT + Q_PROPERTY(uint id READ id CONSTANT) + Q_PROPERTY(QString sender READ sender) + Q_PROPERTY(QString summary READ summary NOTIFY summaryChanged) + Q_PROPERTY(QString body READ body NOTIFY bodyChanged) + Q_PROPERTY(QDateTime timestamp READ timestamp NOTIFY timestampChanged) + Q_PROPERTY(QString icon READ icon NOTIFY iconChanged) + + explicit Notification(QObject *parent = 0); + +public: + inline uint id() const { return _id; } + inline QString sender() const { return _sender; } + inline QString summary() const { return _summary; } + inline QString body() const { return _body; } + inline QDateTime timestamp() const { return _timestamp; } + inline QString icon() const { return _icon; } + +signals: + void summaryChanged(); + void bodyChanged(); + void timestampChanged(); + void iconChanged(); + + void closed(int reason); + +private: + friend class NotificationMonitor; + + uint _id; + QString _sender; + QString _summary; + QString _body; + QDateTime _timestamp; + QString _icon; +}; + +} + +#endif // WATCHFISH_NOTIFICATION_H diff --git a/notificationmonitor.cpp b/notificationmonitor.cpp new file mode 100644 index 0000000..339f43c --- /dev/null +++ b/notificationmonitor.cpp @@ -0,0 +1,322 @@ +#include +#include +#include + +#include "notification.h" +#include "notificationmonitor.h" + +using namespace watchfish; + +namespace +{ + +NotificationMonitor *global_monitor = 0; + +DBusConnection *bus_connection; + +QHash pending_confirmation; + +dbus_bool_t bus_watch_add(DBusWatch *watch, void *data) +{ + NotificationMonitor *monitor = static_cast(data); + int socket = dbus_watch_get_socket(watch); + int flags = dbus_watch_get_flags(watch); + QSocketNotifier::Type type; + switch (flags) { + case DBUS_WATCH_READABLE: + type = QSocketNotifier::Read; + break; + case DBUS_WATCH_WRITABLE: + type = QSocketNotifier::Write; + break; + default: + qWarning() << "Can't add this type of watch" << flags; + return FALSE; + } + + QSocketNotifier *notifier = new QSocketNotifier(socket, type, monitor); + dbus_watch_set_data(watch, notifier, NULL); + + notifier->setEnabled(dbus_watch_get_enabled(watch)); + + notifier->connect(notifier, &QSocketNotifier::activated, + [watch]() { + dbus_watch_handle(watch, dbus_watch_get_flags(watch)); + + while (dbus_connection_get_dispatch_status(bus_connection) == DBUS_DISPATCH_DATA_REMAINS) { + dbus_connection_dispatch(bus_connection); + } + }); + + return TRUE; +} + +void bus_watch_remove(DBusWatch *watch, void *data) +{ + QSocketNotifier *notifier = static_cast(dbus_watch_get_data(watch)); + Q_UNUSED(data); + delete notifier; +} + +void bus_watch_toggle(DBusWatch *watch, void *data) +{ + QSocketNotifier *notifier = static_cast(dbus_watch_get_data(watch)); + Q_UNUSED(data); + notifier->setEnabled(dbus_watch_get_enabled(watch)); +} + +QVariantHash parse_notify_call(DBusMessage *msg) +{ + QVariantHash r; + DBusMessageIter iter, sub; + const char *app_name, *app_icon, *summary, *body; + quint32 replaces_id; + qint32 expire_timeout; + + if (strcmp(dbus_message_get_signature(msg), "susssasa{sv}i") != 0) { + qWarning() << "Invalid signature"; + return r; + } + + dbus_message_iter_init(msg, &iter); + Q_ASSERT(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&iter, &app_name); + dbus_message_iter_next(&iter); + Q_ASSERT(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32); + dbus_message_iter_get_basic(&iter, &replaces_id); + dbus_message_iter_next(&iter); + Q_ASSERT(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&iter, &app_icon); + dbus_message_iter_next(&iter); + dbus_message_iter_get_basic(&iter, &summary); + dbus_message_iter_next(&iter); + dbus_message_iter_get_basic(&iter, &body); + dbus_message_iter_next(&iter); + + QStringList actions; + dbus_message_iter_recurse(&iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + const char *action; + dbus_message_iter_get_basic(&sub, &action); + actions.append(QString::fromUtf8(action)); + dbus_message_iter_next(&sub); + } + r.insert("actions", QVariant::fromValue(actions)); + dbus_message_iter_next(&iter); + + dbus_message_iter_recurse(&iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + const char *key; + + dbus_message_iter_recurse(&sub, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + dbus_message_iter_recurse(&entry, &value); + if (strcmp(key, "category") == 0 && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRING) { + const char *s; + dbus_message_iter_get_basic(&value, &s); + r.insert("category", QString::fromUtf8(s)); + } else if (strcmp(key, "x-nemo-timestamp") == 0 && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRING) { + const char *s; + dbus_message_iter_get_basic(&value, &s); + r.insert("timestamp", QDateTime::fromString(QString::fromUtf8(s), Qt::ISODate)); + } + + dbus_message_iter_next(&sub); + } + + dbus_message_iter_next(&iter); + Q_ASSERT(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_INT32); + dbus_message_iter_get_basic(&iter, &expire_timeout); + + r.insert("sender", QString::fromUtf8(app_name)); + r.insert("app_icon", QString::fromUtf8(app_icon)); + r.insert("summary", QString::fromUtf8(summary)); + r.insert("body", QString::fromUtf8(body)); + + if (strlen(app_icon) > 0) { + r.insert("icon", QString::fromLocal8Bit(app_icon)); + } + + return r; +} + +DBusHandlerResult message_filter(DBusConnection *conn, DBusMessage *msg, void *user_data) +{ + NotificationMonitor *monitor = static_cast(user_data); + DBusError error = DBUS_ERROR_INIT; + Q_UNUSED(conn); + switch (dbus_message_get_type(msg)) { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + if (dbus_message_is_method_call(msg, "org.freedesktop.Notifications", "Notify")) { + quint32 serial = dbus_message_get_serial(msg); + QVariantHash content = parse_notify_call(msg); + pending_confirmation.insert(serial, content); + } + break; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + if (pending_confirmation.contains(dbus_message_get_reply_serial(msg))) { + quint32 id; + if (dbus_message_get_args(msg, &error, DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) { + QVariantHash content = pending_confirmation.take(dbus_message_get_reply_serial(msg)); + monitor->processIncomingNotification(id, content); + } else { + qWarning() << "Could not parse notification method return"; + } + } + break; + case DBUS_MESSAGE_TYPE_SIGNAL: + if (dbus_message_is_signal(msg, "org.freedesktop.Notifications", "NotificationClosed")) { + quint32 id, reason; + if (dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_UINT32, &reason, + DBUS_TYPE_INVALID)) { + monitor->processCloseNotification(id, reason); + } else { + qWarning() << "Failed to parse notification signal arguments"; + } + + } + break; + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +void send_message_with_string(const char *service, const char *path, const char *iface, const char *method, const char *arg) +{ + DBusMessage *msg = dbus_message_new_method_call(service, path, iface, method); + Q_ASSERT(msg); + dbus_message_set_no_reply(msg, TRUE); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &arg, + DBUS_TYPE_INVALID); + dbus_connection_send(bus_connection, msg, NULL); + dbus_message_unref(msg); +} + +void add_match_rule(const char *rule) +{ + send_message_with_string("org.freedesktop.DBus", "/", + "org.freedesktop.DBus", "AddMatch", rule); +} + +void remove_match_rule(const char *rule) +{ + send_message_with_string("org.freedesktop.DBus", "/", + "org.freedesktop.DBus", "RemoveMatch", rule); +} + +} + +NotificationMonitor::NotificationMonitor(QObject *parent) : + QObject(parent) +{ + Q_ASSERT(!bus_connection); + DBusError error = DBUS_ERROR_INIT; + bus_connection = dbus_bus_get_private(DBUS_BUS_SESSION, &error); + if (!bus_connection) { + qWarning() << "Could not connect to the session bus"; + return; + } + + dbus_connection_set_exit_on_disconnect(bus_connection, FALSE); + + dbus_connection_set_watch_functions(bus_connection, bus_watch_add, + bus_watch_remove, bus_watch_toggle, + this, NULL); + + add_match_rule("type='method_call',interface='org.freedesktop.Notifications',member='Notify',eavesdrop='true'"); + add_match_rule("type='method_return',sender='org.freedesktop.Notifications',eavesdrop='true'"); + add_match_rule("type='signal',sender='org.freedesktop.Notifications',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications',member='NotificationClosed'"); + + dbus_bool_t result = dbus_connection_add_filter(bus_connection, message_filter, + this, NULL); + if (!result) { + qWarning() << "Could not add filter"; + } + + qDebug() << "Starting notification monitor"; +} + +NotificationMonitor::~NotificationMonitor() +{ + Q_ASSERT(bus_connection); + + remove_match_rule("type='method_call',interface='org.freedesktop.Notifications',member='Notify',eavesdrop='true'"); + remove_match_rule("type='method_return',sender='org.freedesktop.Notifications',eavesdrop='true'"); + remove_match_rule("type='signal',sender='org.freedesktop.Notifications',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications',member='NotificationClosed'"); + + dbus_connection_remove_filter(bus_connection, message_filter, this); + + dbus_connection_close(bus_connection); + dbus_connection_unref(bus_connection); + bus_connection = NULL; +} + +NotificationMonitor *NotificationMonitor::instance() +{ + if (!global_monitor) { + global_monitor = new NotificationMonitor; + } + return global_monitor; +} + +void NotificationMonitor::processIncomingNotification(quint32 id, const QVariantHash &content) +{ + qDebug() << "Incoming notification" << id << content; + Notification *n = _notifs.value(id, 0); + if (n) { + QString s = content["summary"].toString(); + if (n->_summary != s) { + n->_summary = s; + emit n->summaryChanged(); + } + s = content["body"].toString(); + if (n->_body != s) { + n->_body = s; + emit n->bodyChanged(); + } + s = content["icon"].toString(); + if (n->_icon != s) { + n->_icon = s; + emit n->iconChanged(); + } + QDateTime dt = content["timestamp"].toDateTime(); + if (dt.isValid() && n->_timestamp != dt) { + n->_timestamp = dt; + emit n->timestampChanged(); + } + } else { + n = new Notification(this); + n->_id = id; + n->_sender = content["sender"].toString(); + n->_summary = content["summary"].toString(); + n->_body = content["body"].toString(); + n->_timestamp = content["timestamp"].toDateTime(); + n->_icon = content["icon"].toString(); + + if (!n->_timestamp.isValid()) { + n->_timestamp = QDateTime::currentDateTime(); + } + + _notifs.insert(id, n); + + emit notification(n); + } +} + +void NotificationMonitor::processCloseNotification(quint32 id, quint32 reason) +{ + qDebug() << "Close notification" << id << reason; + Notification *n = _notifs.value(id, 0); + if (n) { + _notifs.remove(id); + emit n->closed(reason); + n->deleteLater(); + } else { + qDebug() << " but it is not found"; + } +} diff --git a/notificationmonitor.h b/notificationmonitor.h new file mode 100644 index 0000000..8563053 --- /dev/null +++ b/notificationmonitor.h @@ -0,0 +1,34 @@ +#ifndef NOTIFICATIONMONITOR_H +#define NOTIFICATIONMONITOR_H + +#include +#include + +namespace watchfish +{ + +class Notification; + +class NotificationMonitor : public QObject +{ + Q_OBJECT + +public: + ~NotificationMonitor(); + + static NotificationMonitor *instance(); + + void processIncomingNotification(quint32 id, const QVariantHash &content); + void processCloseNotification(quint32 id, quint32 reason); + +signals: + void notification(Notification *n); + +private: + explicit NotificationMonitor(QObject *parent = 0); + QMap _notifs; +}; + +} + +#endif // NOTIFICATIONMONITOR_H diff --git a/watchfish.pro b/watchfish.pro deleted file mode 100644 index 071e35a..0000000 --- a/watchfish.pro +++ /dev/null @@ -1,4 +0,0 @@ -TARGET = watchfish -TEMPLATE = lib -CONFIG += staticlib - -- cgit v1.2.3