diff --git a/README b/README index 37b9058..e8e76f1 100644 --- a/README +++ b/README @@ -27,19 +27,32 @@ https://sylvaindurand.org/update-notifications-with-libnotify/ To test the implementation use following: +Arguments: + application name = identifier + id - if 0 or not existing will create new notification, otherwise + icon - local path if provided - the text is not displayed + title + desctiotion + actions + hints + expire time (milisec) - if -1 server default (5sec), if 0 - no expiration + gdbus call \ --session \ --dest org.freedesktop.Notifications \ --object-path /org/freedesktop/Notifications \ --method org.freedesktop.Notifications.Notify \ "identifier" \ - "1" \ + "0" \ "" \ "Notification title" \ "Notification description" \ "[]" \ "{}" \ "2000" + +In d-feet + "identifier", 0, "", "Notification title", "Notification description", [], {}, 30000 The second identifier (here 1) concerns the id of the notification we wish to replace, we will come back to this shortly. @@ -48,9 +61,8 @@ The next identifier allows you to define an icon. and finally the last argument 2000 presents the time during which the notification must remain visible (in milliseconds). -Once this command is executed, the system returns a response that looks like : - -(uint32 13,) +Once this command is executed, the system returns a response: + (uint32 13,) This number, here 13, is the id of the notification that we will be able to replace. diff --git a/src/daemon/NotificationsService.cpp b/src/daemon/NotificationsService.cpp index 9ee757b..7709965 100644 --- a/src/daemon/NotificationsService.cpp +++ b/src/daemon/NotificationsService.cpp @@ -23,25 +23,26 @@ * */ +#include + #include "NotificationsService.h" -#define NOTIFICATIONS_DBUS_PATH "/org/freedesktop/Notifications" #define IMAGE_SIZE 48 -#define SRV_VERSION "1.1" -#define SPEC_VERSION "1.1" +#define SRV_VERSION "1.2" +#define SPEC_VERSION "1.2" #define NOTIFICATIONS_NAME "Notification Daemon" #define TRINITY_DESKTOP_PROJECT "Trinity Desktop Project" NotificationsService::NotificationsService(TQT_DBusConnection &conn) -: org::freedesktop::NotificationsInterface(), mConnection(&conn) +: org::freedesktop::NotificationsInterface(), mConnection(&conn), + mNotificationId(0) { - // TODO Auto-generated constructor stub } NotificationsService::~NotificationsService() { - // TODO Auto-generated destructor stub + notificationMap.clear(); } void NotificationsService::closeNotifyWidget(TQ_UINT32 id, TQ_UINT32 reason) { @@ -57,20 +58,22 @@ void NotificationsService::closeNotifyWidget(TQ_UINT32 id, TQ_UINT32 reason) { } bool NotificationsService::handleSignalSend(const TQT_DBusMessage& reply) { - mConnection->send(reply); return true; } TQString NotificationsService::objectPath() const { - return TQString(NOTIFICATIONS_DBUS_PATH); } bool NotificationsService::GetCapabilities(TQStringList& return_caps, TQT_DBusError& error) { return_caps.clear(); - return_caps << "action-icons" << "actions" << "body" << "body-hyperlinks" << "body-markup" << "icon-static"; + // action-icons, actions are not implemented + // hints are partially implemented (see Capabilities in the docs) + // - persistence and sound + return_caps << "body" << "body-hyperlinks" << "body-images" << + "body-markup" << "icon-static" << "persistence" << "sound"; return true; } @@ -97,38 +100,115 @@ bool NotificationsService::GetServerInformation(TQString& return_name, TQString& void NotificationsService::NotifyAsync( int asyncCallId, - const TQString& app_name, TQ_UINT32 id, const TQString& icon, - const TQString& summary, const TQString& body, + const TQString& app_name, + TQ_UINT32 id, + const TQString& icon, + const TQString& summary, + const TQString& body, const TQStringList& actions, - const TQMap& hints, TQ_INT32 timeout) + const TQMap& hints, + TQ_INT32 timeout) { - if (notificationMap.contains(id)) + TQ_UINT32 nId=id; +// if (nId != 0 && !notificationMap.contains(nId)) +// tqDebug("Requested id %i is not valid", nId); + bool found = notificationMap.contains(nId); + if (nId == 0 || !notificationMap.contains(nId)) // new notification + { + nId = ++mNotificationId; + notificationMap[nId] = new NotifyWidget(0, app_name.ascii(), this, nId); + } + + if(!hints.empty()) { - NotifyAsyncError(asyncCallId, TQT_DBusError::stdFailed("Requested id already displayed")); - tqDebug("Requested id %i already in use", id); - return; + TQString errStr; + TQMap::const_iterator it; + for ( it = hints.begin(); it != hints.end(); ++it ) { + bool ok = true; + if(it.key().latin1()=="category") + { + notificationMap[nId]->setCategory(it.data().value.toString(&ok)); + if(!ok) errStr += " category"; + } + else if (it.key().latin1()=="image-path") + { + notificationMap[nId]->setImage(it.data().value.toString(&ok)); + if(!ok) errStr += " image-path"; + } + else if (it.key().latin1()=="image-data" || it.key().latin1()=="image_data" || it.key().latin1()=="icon_data") + { + notificationMap[nId]->setImageData(it.data().value.toTQValueList(&ok)); + if(!ok) errStr += " image-data"; + } + else if (it.key().latin1()=="sound-file") + { + notificationMap[nId]->setSoundFile(it.data().value.toString(&ok)); + if(!ok) errStr += " sound-file"; + } + else if (it.key().latin1()=="sound-name") + { + notificationMap[nId]->setSoundName(it.data().value.toString(&ok)); + if(!ok) errStr += " sound-name"; + } + else if (it.key().latin1()=="suppress-sound") + { + notificationMap[nId]->setSuppressSound(it.data().value.toBool(&ok)); + if(!ok) errStr += " suppress-sound"; + } + else if (it.key().latin1()=="transient") + { + notificationMap[nId]->setTransient(it.data().value.toBool(&ok)); + if(!ok) errStr += " transient"; + } + else if (it.key().latin1()=="urgency") + { + notificationMap[nId]->setUrgency(it.data().value.toUInt16(&ok)); + if(!ok) errStr += " urgency"; + } + else if (it.key().latin1()=="sender-pid") + { + notificationMap[nId]->setSenderPid(it.data().value.toUInt64(&ok)); + if(!ok) errStr += " sender-pid"; + } + } + if(! errStr.isNull() ) + tqDebug("There was an error converting some of the hint values:" + errStr); } - notificationMap[id] = new NotifyWidget(0, app_name.ascii(), this, id ); - notificationMap[id]->setFrameStyle( TQFrame::NoFrame ); - notificationMap[id]->setPaletteBackgroundColor(TQt::black); - notificationMap[id]->setPaletteForegroundColor(TQt::white); + notificationMap[nId]->setFrameStyle( TQFrame::NoFrame ); +// notificationMap[nId]->setPaletteBackgroundColor(TQt::black); +// notificationMap[nId]->setPaletteForegroundColor(TQt::white); - if (icon.isEmpty() || ! notificationMap[id]->setIcon(icon)) { - notificationMap[id]->setTextFormat(TQt::RichText); - notificationMap[id]->setText(app_name + ":\n" + summary + "\n" + body); + if (icon.isEmpty() || ! notificationMap[nId]->setIcon(icon)) { + notificationMap[nId]->setTextFormat(TQt::RichText); + notificationMap[nId]->setText(app_name + ":\n" + summary + "\n" + body); + } + notificationMap[nId]->setActions(actions); + notificationMap[nId]->setTimeout(timeout); + notificationMap[nId]->adjustSize(); + notificationMap[nId]->raise(); + notificationMap[nId]->show(); + notificationMap[nId]->setActiveWindow(); + + // make sure we display the new notification above the older one + // and if we reach the top of the screen we start at the bottom + TQDesktopWidget *d = TQApplication::desktop(); + if (notificationMap.contains(nId-1) && notificationMap[nId-1] != 0) { + TQPoint pos = notificationMap[nId-1]->pos(); + if(pos.y()-notificationMap[nId-1]->height() < 0) + pos.setY(d->height()-notificationMap[nId]->height()); + pos.setX(d->width()-notificationMap[nId]->width()); + notificationMap[nId]->move(pos.x(),pos.y()-notificationMap[nId-1]->height()); + } else { + notificationMap[nId]->move( d->width()-notificationMap[nId]->width(), + d->height()-notificationMap[nId]->height()); } - notificationMap[id]->setActions(actions); - notificationMap[id]->setHints(hints); - notificationMap[id]->setTimeout(timeout); - notificationMap[id]->adjustSize(); - notificationMap[id]->raise(); - notificationMap[id]->show(); - notificationMap[id]->setActiveWindow(); - - NotifyAsyncReply(asyncCallId, id); + + NotifyAsyncReply(asyncCallId, nId); } void NotificationsService::handleMethodReply(const TQT_DBusMessage& reply) { mConnection->send(reply); } + + diff --git a/src/daemon/NotificationsService.h b/src/daemon/NotificationsService.h index c461b34..874eda7 100644 --- a/src/daemon/NotificationsService.h +++ b/src/daemon/NotificationsService.h @@ -26,7 +26,6 @@ #ifndef NOTIFICATIONSSERVICE_H_ #define NOTIFICATIONSSERVICE_H_ - #include #include #include @@ -35,6 +34,9 @@ #include "notificationsInterface.h" #include "NotifyWidget.h" +#define NOTIFICATIONS_DBUS_PATH "/org/freedesktop/Notifications" +#define NOTIFICATIONS_DBUS_SRVC "org.freedesktop.Notifications" + class NotificationsService: public org::freedesktop::NotificationsInterface { public: @@ -50,6 +52,18 @@ protected: // implement sending signals protected: // implement DBus calls + /** + * Capabilities + * + * "body" + * "body-hyperlinks" + * "body-images" + * "body-markup" + * "icon-static" + * "persistence" + * "sound" + * + */ virtual bool GetCapabilities(TQStringList& return_caps, TQT_DBusError& error); virtual void CloseNotificationAsync(int asyncCallId, TQ_UINT32 id); virtual bool ReloadSettings(TQT_DBusError& error); @@ -60,6 +74,7 @@ protected: private: TQT_DBusConnection *mConnection; + TQ_UINT32 mNotificationId; TQMap notificationMap; }; diff --git a/src/daemon/NotifyWidget.cpp b/src/daemon/NotifyWidget.cpp index 482fe84..78d8445 100644 --- a/src/daemon/NotifyWidget.cpp +++ b/src/daemon/NotifyWidget.cpp @@ -21,55 +21,41 @@ */ #include -#include #include -#include +#include #include "NotifyWidget.h" #include "NotificationsService.h" #define FADE_SPEED 5 +#define EXPTIMEOUT 15000 //msec NotifyWidget::NotifyWidget(TQWidget *parent, const char *name, NotificationsService *ns, TQ_INT32 id ) -: TQLabel( parent, name, WStyle_Customize | TQt::WStyle_StaysOnTop | TQt::WStyle_NoBorder), - mName(TQString(name)), notificationService(ns), mId(id) + : TQLabel( parent, name, WStyle_Customize | TQt::WStyle_StaysOnTop | TQt::WStyle_NoBorder), + mName(TQString(name)), + notificationService(ns), + mId(id), + mUrgency(0), + mPersistence(false), + mSuppressSound(false), + mTransient(false), + mSenderPid(0) { - // TODO Auto-generated constructor stub - TQDesktopWidget *d = TQApplication::desktop(); - mPosition=TQPoint(d->width()-50, d->height()-20); - move(mPosition); // TODO: give the user an option to configure if he/she wants to have // the notification fading away from down to top // TQTimer::singleShot(100, this, TQ_SLOT(fadeAway())); + mTimer = new TQTimer( this ); + connect( mTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotTimeout()) ); } NotifyWidget::~NotifyWidget() { + if(mTimer) + delete mTimer; if(notificationService) delete notificationService; } -void NotifyWidget::mousePressEvent( TQMouseEvent *e ) -{ - if (e->button() == TQMouseEvent::LeftButton) - { - // The notification was dismissed by the user. - notificationService->closeNotifyWidget(mId, 2); - } -} - -void NotifyWidget::timeout() -{ - notificationService->closeNotifyWidget(mId, 1); // The notification expired. -} - -void NotifyWidget::fadeAway() -{ - mPosition.setY(mPosition.y()-1); - move(mPosition); - TQTimer::singleShot(FADE_SPEED, this, TQ_SLOT(fadeAway())); -} - void NotifyWidget::setAutoMask(bool b) { if (b) @@ -91,19 +77,114 @@ bool NotifyWidget::setIcon(const TQString& icon) { return true; } +/** + * The actions send a request message back to the notification client when invoked. This functionality may not be implemented by the notification server, conforming clients should check if it is available before using it (see the GetCapabilities message in Protocol). An implementation is free to ignore any requested by the client. As an example one possible rendering of actions would be as buttons in the notification popup. + +Actions are sent over as a list of pairs. Each even element in the list (starting at index 0) represents the identifier for the action. Each odd element in the list is the localized string that will be displayed to the user. + +The default action (usually invoked by clicking the notification) should have a key named "default". The name can be anything, though implementations are free not to display it. + */ void NotifyWidget::setActions(const TQStringList& actions) { mActions = actions; - // TODO handle actions + // not implemented +} + +/** + * Hints are a way to provide extra data to a notification server that the server may be able to make use of. + +Neither clients nor notification servers are required to support any hints. Both sides should assume that hints are not passed, and should ignore any hints they do not understand. + */ + +/** + * Notifications can optionally have a type indicator. Although neither client or nor server must support this, some may choose to. Those servers implementing categories may use them to intelligently display the notification in a certain way, or group notifications of similar types. + */ +void NotifyWidget::setCategory(const TQString& c) { + mCategory = c; +} + +void NotifyWidget::setPersistence(bool p) { + mPersistence = p; +} + +void NotifyWidget::setImage(const TQString& i) { + mIcon = i; +} +void NotifyWidget::setImageData(const TQValueList& data) { + + int x = data[0].toInt32(); + int y = data[1].toInt32(); + int r = data[2].toInt32(); // rowstride + int a = data[3].toBool(); // alpha + int b = data[4].toInt32(); // bits per sample + int c = data[5].toInt32(); // channels + TQValueList v = data[6].toTQValueList(); // image bytes +// int w, int h, int depth, int numColors = 0, Endian bitOrder = IgnoreEndian + mImageData = TQPixmap(x,y); + TQByteArray i; + TQValueList::Iterator it; + TQ_UINT32 count; + for ( it = v.begin(); it != v.end(); ++it ) + i[count++]=(*it).toByte(); + mImageData.loadFromData(i); + setPixmap(mImageData); } -void NotifyWidget::setHints(const TQMap< TQString, TQT_DBusVariant >& hints) { - mHints = hints; - // TODO handle hints +void NotifyWidget::setSoundFile(const TQString& f) { + mSoundFile = f; +} + +void NotifyWidget::setSoundName(const TQString& n) { + mSoundName = n; +} + +void NotifyWidget::setSuppressSound(bool s) { + mSuppressSound = s; +} + +void NotifyWidget::setTransient(bool t) { + mTransient = t; +} + +void NotifyWidget::setUrgency(TQ_UINT16 l) { + mUrgency = l; +} + + +void NotifyWidget::setSenderPid(TQ_UINT64 p) { + mSenderPid = p; } void NotifyWidget::setTimeout(TQ_INT32 t) { - TDEApplication::kApplication()->setTopWidget(this); - TQTimer::singleShot(t, this, TQ_SLOT(timeout())); + if(t == -1) + { + t = EXPTIMEOUT; + } + if (t > 0) { + mTimer->start( t, TRUE ); + } +} + +void NotifyWidget::mousePressEvent( TQMouseEvent *e ) +{ + if (e->button() == TQMouseEvent::LeftButton) + { + if(mTimer->isActive()) + mTimer->stop(); + notificationService->closeNotifyWidget(mId, 2); // The notification was dismissed by the user (2). + } +} + +void NotifyWidget::slotTimeout() +{ + notificationService->closeNotifyWidget(mId, 1); // The notification expired (1). +} + +void NotifyWidget::slotFadeAway() +{ + TQPoint p = pos(); + p.setY(p.y()-1); + move(p); + TQTimer::singleShot(FADE_SPEED, this, TQ_SLOT(fadeAway())); } #include "NotifyWidget.moc" diff --git a/src/daemon/NotifyWidget.h b/src/daemon/NotifyWidget.h index 08a2f9e..80be6c7 100644 --- a/src/daemon/NotifyWidget.h +++ b/src/daemon/NotifyWidget.h @@ -25,6 +25,7 @@ #include #include +#include #include class NotificationsService; @@ -37,29 +38,97 @@ public: NotifyWidget(TQWidget *parent=0, const char *name=0, NotificationsService *ns=0, TQ_INT32 id=0 ); virtual ~NotifyWidget(); + enum UrgencyLevel { Low, Normal, Critical }; + void setAutoMask(bool b); bool setIcon(const TQString& icon); + /** + * The actions send a request message back to the notification client when invoked. This functionality may not be implemented by the notification server, conforming clients should check if it is available before using it (see the GetCapabilities message in Protocol). An implementation is free to ignore any requested by the client. As an example one possible rendering of actions would be as buttons in the notification popup. + +Actions are sent over as a list of pairs. Each even element in the list (starting at index 0) represents the identifier for the action. Each odd element in the list is the localized string that will be displayed to the user. + +The default action (usually invoked by clicking the notification) should have a key named "default". The name can be anything, though implementations are free not to display it. + */ void setActions(const TQStringList& actions); - void setHints(const TQMap< TQString, TQT_DBusVariant >& hints); + + /** + * Hints are a way to provide extra data to a notification server that the server may be able to make use of. + * Hints + * Category + * Notifications can optionally have a type indicator. Although neither client or nor server must support this, some may choose to. Those servers implementing categories may use them to intelligently display the notification in a certain way, or group notifications of similar types. + +Categories are in class.specific form. class specifies the generic type of notification, and specific specifies the more specific type of notification. + + * Urgency + * Notifications have an urgency level associated with them. This defines the importance of the notification. + * Developers must use their own judgement when deciding the urgency of a notification. + * Typically, if the majority of programs are using the same level for a specific type of urgency, + * other applications should follow them. + * For low and normal urgencies, server implementations may display the notifications how they choose. + * They should, however, have a sane expiration timeout dependent on the urgency level. + * + * Critical notifications should not automatically expire, as they are things that the user will + * most likely want to know about. They should only be closed when the user dismisses them, + * for example, by clicking on the notification. + + * Persistence + * The server supports persistence of notifications. Notifications will be retained until they are + * acknowledged or removed by the user or recalled by the sender. The presence of this capability + * allows clients to depend on the server to ensure a notification is seen and eliminate the need + * for the client to display a reminding function (such as a status icon) of its own. + * + * image + */ + void setCategory(const TQString&); + void setPersistence(bool); + void setImage(const TQString&); + void setImageData(const TQValueList&); + void setSoundFile(const TQString&); + void setSoundName(const TQString&); + void setSuppressSound(bool); + void setTransient(bool); + void setUrgency(TQ_UINT16); + void setSenderPid(TQ_UINT64); + + /** + * The timeout time in milliseconds since the display of the notification at which the notification should automatically close. + +If -1, the notification's expiration time is dependent on the notification server's settings, and may vary for the type of notification. + +If 0, the notification never expires. + */ void setTimeout(TQ_INT32 t); protected: void mousePressEvent( TQMouseEvent *); private slots: - void timeout(); - void fadeAway(); + void slotTimeout(); + void slotFadeAway(); +/** + * DBus signals + * 1. org.freedesktop.Notifications.NotificationClosed is implemented + * 2. org.freedesktop.Notifications.ActionInvoked is not implemented (see mActions) + * 3. org.freedesktop.Notifications.ActivationToken is not implemented (see mActions) + */ private: NotificationsService *notificationService; - TQPoint mPosition; TQString mName; TQ_INT32 mId; TQString mIcon; - TQStringList mActions; - TQMap< TQString, TQT_DBusVariant > mHints; - + TQStringList mActions; // not implemented + TQString mCategory; // not implemented + TQTimer *mTimer; + TQ_UINT16 mUrgency; + TQPixmap mImageData; + TQString mSoundFile; + TQString mSoundName; + bool mPersistence; + bool mSuppressSound; + bool mTransient; + TQ_UINT64 mSenderPid; }; #endif /* SRC_DAEMON_NOTIFYWIDGET_H_ */ diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index bda9aa5..524f87d 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -41,7 +41,7 @@ main(int argc, char **argv) "deloptes@gmail.com"); TDECmdLineArgs::init( argc, argv, &aboutData ); -// TODO handle options if needed +// no special cmdline options are needed // TDECmdLineArgs::addCmdLineOptions( options ); // KUniqueApplication::addCmdLineOptions(); diff --git a/src/daemon/notificationNodeService.cpp b/src/daemon/notificationNodeService.cpp index 8131f6f..92ec8f2 100644 --- a/src/daemon/notificationNodeService.cpp +++ b/src/daemon/notificationNodeService.cpp @@ -28,7 +28,9 @@ #include "notificationNodeService.h" #include "NotificationsService.h" -#define NOTIFICATIONS_DBUS_PATH "/org/freedesktop/Notifications" + +#define INTROSPECTABLE_DBUS_SRVC "org.freedesktop.DBus.Introspectable" + /* * Root Node */ @@ -90,8 +92,8 @@ NotificationsNodeService::NotificationsNodeService(TQT_DBusConnection &conn) : org::freedesktop::NotificationsNode(), mConnection(conn) { - mInterfaces.insert("org.freedesktop.DBus.Introspectable", this); - mInterfaces.insert("org.freedesktop.Notifications", new NotificationsService(mConnection)); + mInterfaces.insert(INTROSPECTABLE_DBUS_SRVC, this); + mInterfaces.insert(NOTIFICATIONS_DBUS_SRVC, new NotificationsService(mConnection)); registerObject(mConnection,TQString(NOTIFICATIONS_DBUS_PATH)); }