NotificationDaemon: add support for transient notifications

Transient notifications are removed after being shown. If the summary
is being shown while they appear, they are represented in it by a new
source icon.

We always create a new source for new transient notifications to
ensure that they don't replace the latest persistent notification
associated with the source. Because we generally don't want any
new or resident notifications to be replaced by others, associating
multiple notifications with a source is the next thing we will
implement.

https://bugzilla.gnome.org/show_bug.cgi?id=633412
This commit is contained in:
Marina Zhurakhinskaya 2010-12-15 16:30:50 -05:00
parent 2a19d5f143
commit 22f4aabadf
2 changed files with 83 additions and 21 deletions

View File

@ -227,6 +227,8 @@ Notification.prototype = {
this.source = source; this.source = source;
this.urgent = false; this.urgent = false;
this.resident = false; this.resident = false;
// 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
this.isTransient = false;
this.expanded = false; this.expanded = false;
this._useActionIcons = false; this._useActionIcons = false;
this._customContent = false; this._customContent = false;
@ -497,6 +499,10 @@ Notification.prototype = {
this.resident = resident; this.resident = resident;
}, },
setTransient: function(isTransient) {
this.isTransient = isTransient;
},
setUseActionIcons: function(useIcons) { setUseActionIcons: function(useIcons) {
this._useActionIcons = useIcons; this._useActionIcons = useIcons;
}, },
@ -808,6 +814,11 @@ Source.prototype = {
this.title = title; this.title = title;
this._iconBin = new St.Bin({ width: this.ICON_SIZE, this._iconBin = new St.Bin({ width: this.ICON_SIZE,
height: this.ICON_SIZE }); height: this.ICON_SIZE });
this.isTransient = false;
},
setTransient: function(isTransient) {
this.isTransient = isTransient;
}, },
// Called to create a new icon actor (of size this.ICON_SIZE). // Called to create a new icon actor (of size this.ICON_SIZE).
@ -1068,6 +1079,14 @@ MessageTray.prototype = {
} }
this._summaryItems.push(summaryItem); this._summaryItems.push(summaryItem);
// We keep this._newSummaryItems to track any new sources that were added to the
// summary and show the summary with them to the user for a short period of time
// after notifications are done showing. However, we don't want that to happen for
// transient sources, which are removed after the notification is shown, but are
// not removed fast enough because of the callbacks to avoid the summary popping up.
// So we just don't add transient sources to this._newSummaryItems .
if (!source.isTransient)
this._newSummaryItems.push(summaryItem); this._newSummaryItems.push(summaryItem);
source.connect('notify', Lang.bind(this, this._onNotify)); source.connect('notify', Lang.bind(this, this._onNotify));
@ -1601,7 +1620,10 @@ MessageTray.prototype = {
this._notification.collapseCompleted(); this._notification.collapseCompleted();
this._notification.disconnect(this._notificationClickedId); this._notification.disconnect(this._notificationClickedId);
this._notificationClickedId = 0; this._notificationClickedId = 0;
let notification = this._notification;
this._notification = null; this._notification = null;
if (notification.isTransient)
notification.destroy();
}, },
_expandNotification: function(autoExpanding) { _expandNotification: function(autoExpanding) {
@ -1750,6 +1772,8 @@ MessageTray.prototype = {
this._summaryNotificationClickedId = 0; this._summaryNotificationClickedId = 0;
let summaryNotification = this._summaryNotification; let summaryNotification = this._summaryNotification;
this._summaryNotification = null; this._summaryNotification = null;
if (summaryNotification.isTransient && !this._reNotifyWithSummaryNotificationAfterHide)
summaryNotification.destroy();
if (this._reNotifyWithSummaryNotificationAfterHide) { if (this._reNotifyWithSummaryNotificationAfterHide) {
this._onNotify(summaryNotification.source, summaryNotification); this._onNotify(summaryNotification.source, summaryNotification);
this._reNotifyWithSummaryNotificationAfterHide = false; this._reNotifyWithSummaryNotificationAfterHide = false;

View File

@ -175,14 +175,43 @@ NotificationDaemon.prototype = {
} }
}, },
_newSource: function(title, pid) { // Returns the source associated with ndata.notification if it is set.
let source = new Source(title, pid); // Otherwise, returns the source associated with the pid if one is
this._sources[pid] = source; // stored in this._sources and the notification is not transient.
// Otherwise, creates a new source as long as pid is provided.
//
// Either a pid or ndata.notification is needed to retrieve or
// create a source.
_getSource: function(title, pid, ndata) {
if (!pid && !(ndata && ndata.notification))
return null;
// We use notification's source for the notifications we still have
// around that are getting replaced because we don't keep sources
// for transient notifications in this._sources, but we still want
// the notification associated with them to get replaced correctly.
if (ndata && ndata.notification)
return ndata.notification.source;
let isForTransientNotification = (ndata && ndata.hints['transient'] == true);
// We don't want to override a persistent notification
// with a transient one from the same sender, so we
// always create a new source object for new transient notifications
// and never add it to this._sources .
if (!isForTransientNotification && this._sources[pid])
return this._sources[pid];
let source = new Source(title, pid);
source.setTransient(isForTransientNotification);
if (!isForTransientNotification) {
this._sources[pid] = source;
source.connect('destroy', Lang.bind(this, source.connect('destroy', Lang.bind(this,
function() { function() {
delete this._sources[pid]; delete this._sources[pid];
})); }));
}
Main.messageTray.add(source); Main.messageTray.add(source);
return source; return source;
@ -234,7 +263,8 @@ NotificationDaemon.prototype = {
let sender = DBus.getCurrentMessageContext().sender; let sender = DBus.getCurrentMessageContext().sender;
let pid = this._senderToPid[sender]; let pid = this._senderToPid[sender];
let source = pid ? this._sources[pid] : null;
let source = this._getSource(appName, pid, ndata);
if (source) { if (source) {
this._notifyForSource(source, ndata); this._notifyForSource(source, ndata);
@ -255,16 +285,23 @@ NotificationDaemon.prototype = {
if (!ndata) if (!ndata)
return; return;
this._senderToPid[sender] = pid; source = this._getSource(appName, pid, ndata);
source = this._sources[pid];
if (!source) // We only store sender-pid entries for persistent sources.
source = this._newSource(appName, pid); // Removing the entries once the source is destroyed
// would result in the entries associated with transient
// sources removed once the notification is shown anyway.
// However, keeping these pairs would mean that we would
// possibly remove an entry associated with a persistent
// source when a transient source for the same sender is
// distroyed.
if (!source.isTransient) {
this._senderToPid[sender] = pid;
source.connect('destroy', Lang.bind(this, source.connect('destroy', Lang.bind(this,
function() { function() {
delete this._senderToPid[sender]; delete this._senderToPid[sender];
})); }));
}
this._notifyForSource(source, ndata); this._notifyForSource(source, ndata);
})); }));
@ -309,6 +346,9 @@ NotificationDaemon.prototype = {
notification.setUrgent(hints.urgency == Urgency.CRITICAL); notification.setUrgent(hints.urgency == Urgency.CRITICAL);
notification.setResident(hints.resident == true); notification.setResident(hints.resident == true);
// 'transient' is a reserved keyword in JS, so we have to retrieve the value
// of the 'transient' hint with hints['transient'] rather than hints.transient
notification.setTransient(hints['transient'] == true);
let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null; let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
source.notify(notification, sourceIconActor); source.notify(notification, sourceIconActor);
@ -378,9 +418,7 @@ NotificationDaemon.prototype = {
}, },
_onTrayIconAdded: function(o, icon) { _onTrayIconAdded: function(o, icon) {
let source = this._sources[icon.pid]; let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null);
if (!source)
source = this._newSource(icon.title || icon.wm_class || _("Unknown"), icon.pid);
source.setTrayIcon(icon); source.setTrayIcon(icon);
}, },