From 77ed621c741e5e1488e9e4842d6843465c7f5a42 Mon Sep 17 00:00:00 2001 From: Marina Zhurakhinskaya Date: Mon, 22 Feb 2010 17:19:32 -0500 Subject: [PATCH] Associate sources with applications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the "appName" parameter in notifications to identify the source rather than the id - use the latter to enable update and removal of individual notifications as laid out in the desktop notification spec. This is a rebase of the patch by Florian Müllner. --- js/ui/messageTray.js | 69 ++++++++++++++++++++++++++++++++----- js/ui/notificationDaemon.js | 56 +++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 26 deletions(-) diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 62011a287..a1f6509af 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -36,6 +36,7 @@ function _cleanMarkup(text) { } // Notification: +// @id: the notification's id // @source: the notification's Source // @title: the title // @banner: the banner text @@ -55,15 +56,21 @@ function _cleanMarkup(text) { // of the notification (as with addBody()) when the banner is expanded. // In this case, if @banner is too long to fit in the single-line mode, // the notification will be made expandable automatically. -function Notification(source, title, banner, bannerBody) { - this._init(source, title, banner, bannerBody); +function Notification(id, source, title, banner, bannerBody) { + this._init(id, source, title, banner, bannerBody); } Notification.prototype = { - _init: function(source, title, banner, bannerBody) { + _init: function(id, source, title, banner, bannerBody) { + this.id = id; this.source = source; this._bannerBody = bannerBody; + source.connect('clicked', Lang.bind(this, + function() { + this.emit('dismissed'); + })); + this.actor = new St.Table({ name: 'notification' }); this.update(title, banner, true); }, @@ -303,6 +310,10 @@ Notification.prototype = { time: ANIMATION_TIME, transition: "easeOutQuad" }); return true; + }, + + destroy: function() { + this.emit('destroy'); } }; Signals.addSignalMethods(Notification.prototype); @@ -380,6 +391,7 @@ MessageTray.prototype = { this._notificationState = State.HIDDEN; this._notificationTimeoutId = 0; this._overviewVisible = false; + this._notificationRemoved = false; this.actor.show(); Main.chrome.addActor(this.actor, { affectsStruts: false, @@ -441,11 +453,11 @@ MessageTray.prototype = { source.connect('destroy', Lang.bind(this, function () { - this.remove(source); + this.removeSource(source); })); }, - remove: function(source) { + removeSource: function(source) { if (!this.contains(source)) return; @@ -458,7 +470,10 @@ MessageTray.prototype = { this._notificationQueue = newNotificationQueue; this._summary.remove_actor(this._icons[source.id]); - this._summaryNeedsToBeShown = true; + if (this._summary.get_children().length > 0) + this._summaryNeedsToBeShown = true; + else + this._summaryNeedsToBeShown = false; delete this._icons[source.id]; delete this._sources[source.id]; @@ -467,16 +482,50 @@ MessageTray.prototype = { Mainloop.source_remove(this._notificationTimeoutId); this._notificationTimeoutId = 0; } + this._notificationRemoved = true; this._updateState(); } }, + removeNotification: function(notification) { + if (this._notification == notification && (this._notificationState == State.SHOWN || this._notificationState == State.SHOWING)) { + if (this._notificationTimeoutId) { + Mainloop.source_remove(this._notificationTimeoutId); + this._notificationTimeoutId = 0; + } + this._notificationRemoved = true; + this._updateState(); + return; + } + + let index = this._notificationQueue.indexOf(notification); + if (index != -1) + this._notificationQueue.splice(index, 1); + }, + getSource: function(id) { return this._sources[id]; }, + _getNotification: function(id, source) { + if (this._notification && this._notification.id == id) + return this._notification; + + for (let i = 0; i < this._notificationQueue.length; i++) { + if (this._notificationQueue[i].id == id && this._notificationQueue[i].source == source) + return this._notificationQueue[i]; + } + + return null; + }, + _onNotify: function(source, notification) { - this._notificationQueue.push(notification); + if (this._getNotification(notification.id, source) == null) { + notification.connect('destroy', + Lang.bind(this, this.removeNotification)); + this._notificationQueue.push(notification); + } + this._updateState(); }, @@ -524,9 +573,9 @@ MessageTray.prototype = { _updateState: function() { // Notifications let notificationsPending = this._notificationQueue.length > 0; - let notificationPinned = this._pointerInTray && !this._pointerInSummary; + let notificationPinned = this._pointerInTray && !this._pointerInSummary && !this._notificationRemoved; let notificationExpanded = this._notificationBin.y < 0; - let notificationExpired = this._notificationTimeoutId == 0 && !this._pointerInTray; + let notificationExpired = (this._notificationTimeoutId == 0 && !this._pointerInTray) || this._notificationRemoved; if (this._notificationState == State.HIDDEN) { if (notificationsPending) @@ -541,6 +590,7 @@ MessageTray.prototype = { // Summary let summarySummoned = this._pointerInSummary || this._overviewVisible; let summaryPinned = this._summaryTimeoutId != 0 || this._pointerInTray || summarySummoned; + let notificationsVisible = (this._notificationState == State.SHOWING || this._notificationState == State.SHOWN); let notificationsDone = !notificationsVisible && !notificationsPending; @@ -638,6 +688,7 @@ MessageTray.prototype = { }, _hideNotification: function() { + this._notificationRemoved = false; this._notification.popIn(); this._tween(this._notificationBin, "_notificationState", State.HIDDEN, diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 6e53f1a7d..deb735226 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -96,6 +96,8 @@ NotificationDaemon.prototype = { DBus.MANY_INSTANCES, Lang.bind(this, this._acquiredName), Lang.bind(this, this._lostName)); + + this._currentNotifications = {}; }, _acquiredName: function() { @@ -124,30 +126,23 @@ NotificationDaemon.prototype = { }, _sourceId: function(id) { - return 'notification-' + id; + return 'source-' + id; }, Notify: function(appName, replacesId, icon, summary, body, actions, hints, timeout) { - let id, source = null; - - if (replacesId != 0) { - id = replacesId; - source = Main.messageTray.getSource(this._sourceId(id)); - // source may be null if the current source was destroyed - // right as the client sent the new notification - } + let source = Main.messageTray.getSource(this._sourceId(appName)); + let id = null; + // Source may be null if we have never received a notification from + // this app or if all notifications from this app have been acknowledged. if (source == null) { - id = nextNotificationId++; - - source = new Source(this._sourceId(id), icon, hints); + source = new Source(this._sourceId(appName), icon, hints); Main.messageTray.add(source); source.connect('clicked', Lang.bind(this, function() { source.destroy(); - this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); })); let sender = DBus.getCurrentMessageContext().sender; @@ -156,6 +151,8 @@ NotificationDaemon.prototype = { let app = Shell.WindowTracker.get_default().get_app_from_pid(result); source.setApp(app); }); + } else { + source.update(icon, hints); } summary = GLib.markup_escape_text(summary, -1); @@ -169,7 +166,27 @@ NotificationDaemon.prototype = { } } - let notification = new MessageTray.Notification(source, summary, body, true); + let notification; + if (replacesId != 0) { + id = replacesId; + notification = this._currentNotifications[id]; + } + + if (notification == null) { + id = nextNotificationId++; + notification = new MessageTray.Notification(id, source, summary, body, true); + this._currentNotifications[id] = notification; + notification.connect('dismissed', Lang.bind(this, + function(n) { + n.destroy(); + this._emitNotificationClosed(n.id, NotificationClosedReason.DISMISSED); + })); + } else { + // passing in true as the last parameter will clear out extra actors, + // such as actions + notification.update(summary, body, true); + } + if (actions.length) { for (let i = 0; i < actions.length - 1; i += 2) notification.addAction(actions[i], actions[i + 1]); @@ -181,9 +198,9 @@ NotificationDaemon.prototype = { }, CloseNotification: function(id) { - let source = Main.messageTray.getSource(this._sourceId(id)); - if (source) - source.destroy(); + let notification = this._currentNotifications[id]; + if (notification) + notification.destroy(); this._emitNotificationClosed(id, NotificationClosedReason.APP_CLOSED); }, @@ -215,6 +232,7 @@ NotificationDaemon.prototype = { }, _emitNotificationClosed: function(id, reason) { + delete this._currentNotifications[id]; DBus.session.emit_signal('/org/freedesktop/Notifications', 'org.freedesktop.Notifications', 'NotificationClosed', 'uu', @@ -241,6 +259,10 @@ Source.prototype = { _init: function(sourceId, icon, hints) { MessageTray.Source.prototype._init.call(this, sourceId); + this.update(icon, hints); + }, + + update: function(icon, hints) { hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true); this._icon = icon;