From 8ceebd625229e18de791ea2cc0445cd6691db069 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 12 Jul 2015 02:18:25 +0200 Subject: load category, add additional nemo fields --- notification.cpp | 123 +++++++++++++++++++++++++++++++++++++ notification.h | 36 +++++++++++ notificationmonitor.cpp | 159 +++++++++++++++++++++++++++++++++++++----------- notificationmonitor.h | 1 + 4 files changed, 284 insertions(+), 35 deletions(-) diff --git a/notification.cpp b/notification.cpp index 51d5325..ba8456e 100644 --- a/notification.cpp +++ b/notification.cpp @@ -16,11 +16,26 @@ * along with this program. If not, see . */ +#include +#include +#include #include "notification.h" namespace watchfish { +namespace +{ +struct Action +{ + QString service; + QString path; + QString iface; + QString method; + QStringList args; +}; +} + struct NotificationPrivate { uint id; @@ -29,6 +44,11 @@ struct NotificationPrivate QString body; QDateTime timestamp; QString icon; + quint8 urgency; + bool transient; + QString previewSummary; + QString previewBody; + QHash actions; }; Notification::Notification(uint id, QObject *parent) : QObject(parent), d_ptr(new NotificationPrivate) @@ -122,4 +142,107 @@ void Notification::setIcon(const QString &icon) } } +int Notification::urgency() const +{ + Q_D(const Notification); + return d->urgency; +} + +void Notification::setUrgency(int urgency) +{ + Q_D(Notification); + if (urgency != d->urgency) { + d->urgency = urgency; + emit urgencyChanged(); + } +} + +bool Notification::transient() const +{ + Q_D(const Notification); + return d->transient; +} + +void Notification::setTransient(bool transient) +{ + Q_D(Notification); + if (transient != d->transient) { + d->transient = transient; + emit transientChanged(); + } +} + +QString Notification::previewSummary() const +{ + Q_D(const Notification); + return d->previewSummary; +} + +void Notification::setPreviewSummary(const QString &summary) +{ + Q_D(Notification); + if (summary != d->previewSummary) { + d->previewSummary = summary; + emit previewSummaryChanged(); + } +} + +QString Notification::previewBody() const +{ + Q_D(const Notification); + return d->previewBody; +} + +void Notification::setPreviewBody(const QString &body) +{ + Q_D(Notification); + if (body != d->previewBody) { + d->previewBody = body; + emit previewBodyChanged(); + } +} + +QStringList Notification::actions() const +{ + Q_D(const Notification); + return d->actions.keys(); +} + +void Notification::addDBusAction(const QString &action, const QString &service, const QString &path, const QString &iface, const QString &method, const QStringList &args) +{ + Q_D(Notification); + Action &a = d->actions[action]; + a.service = service; + a.path = path; + a.iface = iface; + a.method = method; + a.args = args; +} + +void Notification::invokeAction(const QString &action) +{ + Q_D(Notification); + if (d->actions.contains(action)) { + const Action &a = d->actions[action]; + if (!a.service.isEmpty()) { + QDBusMessage msg = QDBusMessage::createMethodCall(a.service, a.path, a.iface, a.method); + foreach (const QString &arg, a.args) { + msg << arg; + } + QDBusConnection::sessionBus().asyncCall(msg); + } + } +} + +void Notification::close() +{ + Q_D(Notification); + QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "CloseNotification"); + msg << quint32(d->id); + QDBusConnection::sessionBus().asyncCall(msg); +} + } diff --git a/notification.h b/notification.h index e5bac6b..dc58c78 100644 --- a/notification.h +++ b/notification.h @@ -21,6 +21,7 @@ #include #include +#include namespace watchfish { @@ -41,6 +42,15 @@ class Notification : public QObject Q_PROPERTY(QDateTime timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged) /** Icon file path */ Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged) + Q_PROPERTY(int urgency READ urgency WRITE setUrgency NOTIFY urgencyChanged) + Q_PROPERTY(bool transient READ transient WRITE setTransient NOTIFY transientChanged) + + /* Nemo stuff */ + Q_PROPERTY(QString previewSummary READ previewSummary WRITE setPreviewSummary NOTIFY previewSummaryChanged) + Q_PROPERTY(QString previewBody READ previewBody WRITE setPreviewBody NOTIFY previewBodyChanged) + + Q_PROPERTY(QStringList actions READ actions NOTIFY actionsChanged) + Q_ENUMS(CloseReason) public: @@ -71,12 +81,38 @@ public: QString icon() const; void setIcon(const QString &icon); + int urgency() const; + void setUrgency(int urgency); + + bool transient() const; + void setTransient(bool transient); + + QString previewSummary() const; + void setPreviewSummary(const QString &summary); + + QString previewBody() const; + void setPreviewBody(const QString &body); + + QStringList actions() const; + void addDBusAction(const QString &action, const QString &service, const QString &path, const QString &iface, const QString &method, const QStringList &args = QStringList()); + +public slots: + void invokeAction(const QString &action); + void close(); + signals: void senderChanged(); void summaryChanged(); void bodyChanged(); void timestampChanged(); void iconChanged(); + void urgencyChanged(); + void transientChanged(); + + void previewSummaryChanged(); + void previewBodyChanged(); + + void actionsChanged(); void closed(CloseReason reason); diff --git a/notificationmonitor.cpp b/notificationmonitor.cpp index 82d70c7..8b30330 100644 --- a/notificationmonitor.cpp +++ b/notificationmonitor.cpp @@ -16,18 +16,56 @@ * along with this program. If not, see . */ +#include #include +#include #include #include #include "notification.h" #include "notificationmonitor.h" +#define CATEGORY_DEFINITION_FILE_DIRECTORY "/usr/share/lipstick/notificationcategories" +#define CATEGORY_REFRESH_CHECK_TIME 120 + namespace watchfish { Q_LOGGING_CATEGORY(notificationMonitorCat, "watchfish-NotificationMonitor") +namespace +{ +struct ProtoNotification +{ + QString sender; + QString appIcon; + QString summary; + QString body; + QHash hints; + int expireTimeout; + QStringList actions; +}; + +QDebug operator<<(QDebug &debug, const ProtoNotification &proto) +{ + QDebugStateSaver saver(debug); + Q_UNUSED(saver); + debug.nospace() << "Notification(sender=" << proto.sender << ", summary=" << proto.summary + << ", body=" << proto.body << ", appIcon=" << proto.appIcon + << ", hints=" << proto.hints << ", timeout=" << proto.expireTimeout + << ", actions=" << proto.actions << ")"; + return debug; +} + +struct CategoryCacheEntry +{ + QHash data; + QDateTime lastReadTime; + QDateTime lastCheckTime; +}; + +} + class NotificationMonitorPrivate { NotificationMonitor * const q_ptr; @@ -38,19 +76,23 @@ class NotificationMonitorPrivate /** Low level dbus connection used for sniffing. */ DBusConnection *_conn; /** Serials of DBUS method calls of which we are expecting a reply. */ - QHash _pending_confirmation; + QHash _pendingConfirmation; + /** Cache of notification category info. */ + mutable QHash _categoryCache; NotificationMonitorPrivate(NotificationMonitor *q); ~NotificationMonitorPrivate(); - void processIncomingNotification(quint32 id, const QVariantHash &content); + void processIncomingNotification(quint32 id, const ProtoNotification &proto); void processCloseNotification(quint32 id, quint32 reason); void sendMessageWithString(const char *service, const char *path, const char *iface, const char *method, const char *arg); void addMatchRule(const char *rule); void removeMatchRule(const char *rule); - QVariantHash parseNotifyCall(DBusMessage *msg) const; + ProtoNotification parseNotifyCall(DBusMessage *msg) const; + + QHash getCategoryInfo(const QString &s) const; static dbus_bool_t busWatchAdd(DBusWatch *watch, void *data); static void busWatchRemove(DBusWatch *watch, void *data); @@ -106,10 +148,10 @@ NotificationMonitorPrivate::~NotificationMonitorPrivate() dbus_connection_unref(_conn); } -void NotificationMonitorPrivate::processIncomingNotification(quint32 id, const QVariantHash &content) +void NotificationMonitorPrivate::processIncomingNotification(quint32 id, const ProtoNotification &proto) { Q_Q(NotificationMonitor); - qCDebug(notificationMonitorCat) << "Incoming notification" << id << content; + qCDebug(notificationMonitorCat) << "Incoming notification" << id << proto; Notification *n = _notifs.value(id, 0); @@ -118,18 +160,35 @@ void NotificationMonitorPrivate::processIncomingNotification(quint32 id, const Q n = new Notification(id, q); } - n->setSender(content["sender"].toString()); - n->setSummary(content["summary"].toString()); - n->setBody(content["body"].toString()); - n->setIcon(content["icon"].toString()); + n->setSender(proto.sender); + n->setSummary(proto.summary); + n->setBody(proto.body); + n->setIcon(proto.appIcon); - QDateTime timestamp = content["timestamp"].toDateTime(); + // Handle nemo specific stuff + QDateTime timestamp = QDateTime::fromString(proto.hints["x-nemo-timestamp"], Qt::ISODate); if (timestamp.isValid()) { n->setTimestamp(timestamp); } else if (is_new_notification) { n->setTimestamp(QDateTime::currentDateTime()); } + n->setPreviewSummary(proto.hints.value("x-nemo-preview-summary")); + n->setPreviewBody(proto.hints.value("x-nemo-preview-body")); + + // Nemo D-Bus actions... + for (int i = 0; i < proto.actions.size(); i += 2) { + const QString &actionName = proto.actions[i]; + QString hintName = QString("x-nemo-remote-action-%1").arg(actionName); + QString remote = proto.hints.value(hintName); + QStringList remoteParts = remote.split(' '); + if (remoteParts.size() >= 4) { + n->addDBusAction(actionName, + remoteParts[0], remoteParts[1], remoteParts[2], remoteParts[3], + remoteParts.mid(4)); + } + } + if (is_new_notification) { _notifs.insert(id, n); emit q->notification(n); @@ -173,9 +232,9 @@ void NotificationMonitorPrivate::removeMatchRule(const char *rule) "org.freedesktop.DBus", "RemoveMatch", rule); } -QVariantHash NotificationMonitorPrivate::parseNotifyCall(DBusMessage *msg) const +ProtoNotification NotificationMonitorPrivate::parseNotifyCall(DBusMessage *msg) const { - QVariantHash r; + ProtoNotification proto; DBusMessageIter iter, sub; const char *app_name, *app_icon, *summary, *body; quint32 replaces_id; @@ -183,7 +242,7 @@ QVariantHash NotificationMonitorPrivate::parseNotifyCall(DBusMessage *msg) const if (strcmp(dbus_message_get_signature(msg), "susssasa{sv}i") != 0) { qCWarning(notificationMonitorCat) << "Invalid signature"; - return r; + return proto; } dbus_message_iter_init(msg, &iter); @@ -201,17 +260,23 @@ QVariantHash NotificationMonitorPrivate::parseNotifyCall(DBusMessage *msg) const dbus_message_iter_get_basic(&iter, &body); dbus_message_iter_next(&iter); - QStringList actions; + // Add basic notification information + proto.sender = QString::fromUtf8(app_name); + proto.appIcon = QString::fromUtf8(app_icon); + proto.summary = QString::fromUtf8(summary); + proto.body = QString::fromUtf8(body); + 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)); + proto.actions.append(QString::fromUtf8(action)); dbus_message_iter_next(&sub); } - r.insert("actions", QVariant::fromValue(actions)); dbus_message_iter_next(&iter); + // Parse extended information + QHash hints; dbus_message_iter_recurse(&iter, &sub); while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry, value; @@ -222,14 +287,10 @@ QVariantHash NotificationMonitorPrivate::parseNotifyCall(DBusMessage *msg) const 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) { + if (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)); + hints.insert(key, QString::fromUtf8(s)); } dbus_message_iter_next(&sub); @@ -238,17 +299,45 @@ QVariantHash NotificationMonitorPrivate::parseNotifyCall(DBusMessage *msg) const 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); + proto.expireTimeout = 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)); + if (hints.contains("category")) { + proto.hints = getCategoryInfo(hints["category"]); + proto.hints.unite(hints); + } else { + proto.hints = hints; } - return r; + return proto; +} + +QHash NotificationMonitorPrivate::getCategoryInfo(const QString &category) const +{ + bool in_cache = _categoryCache.contains(category); + bool needs_check = !in_cache || + _categoryCache[category].lastCheckTime.secsTo(QDateTime::currentDateTime()) > CATEGORY_REFRESH_CHECK_TIME; + if (needs_check) { + QFileInfo finfo(QString("%1/%2.conf").arg(CATEGORY_DEFINITION_FILE_DIRECTORY, category)); + if (finfo.exists()) { + CategoryCacheEntry &entry = _categoryCache[category]; + if (!in_cache || finfo.lastModified() > entry.lastReadTime) { + QSettings settings(finfo.absoluteFilePath(), QSettings::IniFormat); + entry.data.clear(); + foreach (const QString &key, settings.allKeys()) { + entry.data[key] = settings.value(key).toString(); + } + entry.lastReadTime = finfo.lastModified(); + } + entry.lastCheckTime = QDateTime::currentDateTime(); + return entry.data; + } else { + qCWarning(notificationMonitorCat) << "Notification category" << category << "does not exist"; + _categoryCache.remove(category); + return QHash(); + } + } else { + return _categoryCache[category].data; + } } dbus_bool_t NotificationMonitorPrivate::busWatchAdd(DBusWatch *watch, void *data) @@ -306,16 +395,16 @@ DBusHandlerResult NotificationMonitorPrivate::busMessageFilter(DBusConnection *c 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 = self->parseNotifyCall(msg); - self->_pending_confirmation.insert(serial, content); + ProtoNotification proto = self->parseNotifyCall(msg); + self->_pendingConfirmation.insert(serial, proto); } break; case DBUS_MESSAGE_TYPE_METHOD_RETURN: - if (self->_pending_confirmation.contains(dbus_message_get_reply_serial(msg))) { + if (self->_pendingConfirmation.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 = self->_pending_confirmation.take(dbus_message_get_reply_serial(msg)); - self->processIncomingNotification(id, content); + ProtoNotification proto = self->_pendingConfirmation.take(dbus_message_get_reply_serial(msg)); + self->processIncomingNotification(id, proto); } else { qCWarning(notificationMonitorCat) << "Could not parse notification method return"; } diff --git a/notificationmonitor.h b/notificationmonitor.h index 5196170..4337c7f 100644 --- a/notificationmonitor.h +++ b/notificationmonitor.h @@ -42,6 +42,7 @@ public: ~NotificationMonitor(); signals: + /** Emitted when a notification arrives. */ void notification(Notification *n); private: -- cgit v1.2.3