2010-01-13 20:05:20 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const DBus = imports.dbus;
|
|
|
|
const GLib = imports.gi.GLib;
|
|
|
|
const Lang = imports.lang;
|
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
const Mainloop = imports.mainloop;
|
2010-03-08 13:13:59 +00:00
|
|
|
const St = imports.gi.St;
|
2010-10-27 20:00:51 +00:00
|
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
|
|
const _ = Gettext.gettext;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
const MessageTray = imports.ui.messageTray;
|
|
|
|
const Params = imports.misc.params;
|
|
|
|
|
|
|
|
let nextNotificationId = 1;
|
|
|
|
|
2010-02-15 17:27:28 +00:00
|
|
|
// Should really be defined in dbus.js
|
|
|
|
const BusIface = {
|
|
|
|
name: 'org.freedesktop.DBus',
|
|
|
|
methods: [{ name: 'GetConnectionUnixProcessID',
|
|
|
|
inSignature: 's',
|
|
|
|
outSignature: 'i' }]
|
2010-03-15 13:50:05 +00:00
|
|
|
};
|
2010-02-15 17:27:28 +00:00
|
|
|
|
|
|
|
const Bus = function () {
|
|
|
|
this._init();
|
2010-03-15 13:50:05 +00:00
|
|
|
};
|
2010-02-15 17:27:28 +00:00
|
|
|
|
|
|
|
Bus.prototype = {
|
|
|
|
_init: function() {
|
|
|
|
DBus.session.proxifyObject(this, 'org.freedesktop.DBus', '/org/freedesktop/DBus');
|
|
|
|
}
|
2010-03-15 13:50:05 +00:00
|
|
|
};
|
2010-02-15 17:27:28 +00:00
|
|
|
|
|
|
|
DBus.proxifyPrototype(Bus.prototype, BusIface);
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
const NotificationDaemonIface = {
|
|
|
|
name: 'org.freedesktop.Notifications',
|
|
|
|
methods: [{ name: 'Notify',
|
|
|
|
inSignature: 'susssasa{sv}i',
|
|
|
|
outSignature: 'u'
|
|
|
|
},
|
|
|
|
{ name: 'CloseNotification',
|
|
|
|
inSignature: 'u',
|
|
|
|
outSignature: ''
|
|
|
|
},
|
|
|
|
{ name: 'GetCapabilities',
|
|
|
|
inSignature: '',
|
|
|
|
outSignature: 'as'
|
|
|
|
},
|
|
|
|
{ name: 'GetServerInformation',
|
|
|
|
inSignature: '',
|
|
|
|
outSignature: 'ssss'
|
|
|
|
}],
|
|
|
|
signals: [{ name: 'NotificationClosed',
|
|
|
|
inSignature: 'uu' },
|
|
|
|
{ name: 'ActionInvoked',
|
|
|
|
inSignature: 'us' }]
|
|
|
|
};
|
|
|
|
|
|
|
|
const NotificationClosedReason = {
|
|
|
|
EXPIRED: 1,
|
|
|
|
DISMISSED: 2,
|
|
|
|
APP_CLOSED: 3,
|
|
|
|
UNDEFINED: 4
|
|
|
|
};
|
|
|
|
|
|
|
|
const Urgency = {
|
|
|
|
LOW: 0,
|
|
|
|
NORMAL: 1,
|
|
|
|
CRITICAL: 2
|
|
|
|
};
|
|
|
|
|
2010-02-16 22:32:19 +00:00
|
|
|
const rewriteRules = {
|
|
|
|
'XChat': [
|
|
|
|
{ pattern: /^XChat: Private message from: (\S*) \(.*\)$/,
|
2010-11-29 16:12:21 +00:00
|
|
|
replacement: '<$1>' },
|
2010-02-16 22:32:19 +00:00
|
|
|
{ pattern: /^XChat: New public message from: (\S*) \((.*)\)$/,
|
2010-11-29 16:12:21 +00:00
|
|
|
replacement: '$2 <$1>' },
|
2010-02-16 22:32:19 +00:00
|
|
|
{ pattern: /^XChat: Highlighted message from: (\S*) \((.*)\)$/,
|
2010-11-29 16:12:21 +00:00
|
|
|
replacement: '$2 <$1>' }
|
2010-02-16 22:32:19 +00:00
|
|
|
]
|
|
|
|
};
|
2010-06-23 19:20:39 +00:00
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
function NotificationDaemon() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
NotificationDaemon.prototype = {
|
|
|
|
_init: function() {
|
|
|
|
DBus.session.exportObject('/org/freedesktop/Notifications', this);
|
|
|
|
|
|
|
|
this._everAcquiredName = false;
|
|
|
|
DBus.session.acquire_name('org.freedesktop.Notifications',
|
|
|
|
// We pass MANY_INSTANCES so that if
|
|
|
|
// notification-daemon is running, we'll
|
|
|
|
// get queued behind it and then get the
|
|
|
|
// name after killing it below
|
|
|
|
DBus.MANY_INSTANCES,
|
|
|
|
Lang.bind(this, this._acquiredName),
|
|
|
|
Lang.bind(this, this._lostName));
|
2010-02-22 22:19:32 +00:00
|
|
|
|
2010-08-09 17:18:15 +00:00
|
|
|
this._sources = {};
|
2010-09-02 14:45:53 +00:00
|
|
|
this._senderToPid = {};
|
|
|
|
this._notifications = {};
|
|
|
|
this._busProxy = new Bus();
|
2010-02-21 05:25:23 +00:00
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
Main.statusIconDispatcher.connect('message-icon-added', Lang.bind(this, this._onTrayIconAdded));
|
|
|
|
Main.statusIconDispatcher.connect('message-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
|
|
|
|
|
2010-02-21 05:25:23 +00:00
|
|
|
Shell.WindowTracker.get_default().connect('notify::focus-app',
|
|
|
|
Lang.bind(this, this._onFocusAppChanged));
|
2010-02-24 20:46:00 +00:00
|
|
|
Main.overview.connect('hidden',
|
|
|
|
Lang.bind(this, this._onFocusAppChanged));
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_acquiredName: function() {
|
|
|
|
this._everAcquiredName = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
_lostName: function() {
|
|
|
|
if (this._everAcquiredName)
|
|
|
|
log('Lost name org.freedesktop.Notifications!');
|
|
|
|
else if (GLib.getenv('GNOME_SHELL_NO_REPLACE'))
|
|
|
|
log('Failed to acquire org.freedesktop.Notifications');
|
|
|
|
else {
|
|
|
|
log('Failed to acquire org.freedesktop.Notifications; trying again');
|
|
|
|
|
|
|
|
// kill the notification-daemon. pkill is more portable
|
|
|
|
// than killall, but on Linux at least it won't match if
|
|
|
|
// you pass more than 15 characters of the process name...
|
2010-05-13 19:46:04 +00:00
|
|
|
// However, if you use the '-f' flag to match the entire
|
2010-01-13 20:05:20 +00:00
|
|
|
// command line, it will work, but we have to be careful
|
2010-05-13 19:46:04 +00:00
|
|
|
// in that case that we don't match 'gedit
|
|
|
|
// notification-daemon.c' or whatever...
|
2010-01-13 20:05:20 +00:00
|
|
|
let p = new Shell.Process({ args: ['pkill', '-f',
|
|
|
|
'^([^ ]*/)?(notification-daemon|notify-osd)$']});
|
|
|
|
p.run();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-08-05 17:09:27 +00:00
|
|
|
_iconForNotificationData: function(icon, hints, size) {
|
|
|
|
let textureCache = St.TextureCache.get_default();
|
|
|
|
|
|
|
|
if (icon) {
|
|
|
|
if (icon.substr(0, 7) == 'file://')
|
|
|
|
return textureCache.load_uri_async(icon, size, size);
|
|
|
|
else if (icon[0] == '/') {
|
|
|
|
let uri = GLib.filename_to_uri(icon, null);
|
|
|
|
return textureCache.load_uri_async(uri, size, size);
|
|
|
|
} else
|
2010-11-02 22:33:22 +00:00
|
|
|
return new St.Icon({ icon_name: icon,
|
|
|
|
icon_type: St.IconType.FULLCOLOR,
|
|
|
|
icon_size: size });
|
2010-08-05 17:09:27 +00:00
|
|
|
} else if (hints.icon_data) {
|
|
|
|
let [width, height, rowStride, hasAlpha,
|
|
|
|
bitsPerSample, nChannels, data] = hints.icon_data;
|
|
|
|
return textureCache.load_from_raw(data, data.length, hasAlpha,
|
|
|
|
width, height, rowStride, size);
|
|
|
|
} else {
|
|
|
|
let stockIcon;
|
|
|
|
switch (hints.urgency) {
|
|
|
|
case Urgency.LOW:
|
|
|
|
case Urgency.NORMAL:
|
|
|
|
stockIcon = 'gtk-dialog-info';
|
|
|
|
break;
|
|
|
|
case Urgency.CRITICAL:
|
|
|
|
stockIcon = 'gtk-dialog-error';
|
|
|
|
break;
|
|
|
|
}
|
2010-11-02 22:33:22 +00:00
|
|
|
return new St.Icon({ icon_name: stockIcon,
|
|
|
|
icon_type: St.IconType.FULLCOLOR,
|
|
|
|
icon_size: size });
|
2010-08-05 17:09:27 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-12-15 21:30:50 +00:00
|
|
|
// Returns the source associated with ndata.notification if it is set.
|
|
|
|
// Otherwise, returns the source associated with the pid if one is
|
|
|
|
// 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];
|
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
let source = new Source(title, pid);
|
2010-12-15 21:30:50 +00:00
|
|
|
source.setTransient(isForTransientNotification);
|
2010-08-09 16:33:34 +00:00
|
|
|
|
2010-12-15 21:30:50 +00:00
|
|
|
if (!isForTransientNotification) {
|
|
|
|
this._sources[pid] = source;
|
|
|
|
source.connect('destroy', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
delete this._sources[pid];
|
|
|
|
}));
|
|
|
|
}
|
2010-08-09 16:33:34 +00:00
|
|
|
|
|
|
|
Main.messageTray.add(source);
|
|
|
|
return source;
|
|
|
|
},
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
Notify: function(appName, replacesId, icon, summary, body,
|
|
|
|
actions, hints, timeout) {
|
2010-09-02 14:45:53 +00:00
|
|
|
let id;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-02 15:21:47 +00:00
|
|
|
// Filter out notifications from Empathy, since we
|
|
|
|
// handle that information from telepathyClient.js
|
|
|
|
if (appName == 'Empathy') {
|
2010-09-02 14:45:53 +00:00
|
|
|
// Ignore replacesId since we already sent back a
|
|
|
|
// NotificationClosed for that id.
|
2010-02-02 15:21:47 +00:00
|
|
|
id = nextNotificationId++;
|
|
|
|
Mainloop.idle_add(Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
|
|
|
|
}));
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2010-02-16 22:32:19 +00:00
|
|
|
let rewrites = rewriteRules[appName];
|
|
|
|
if (rewrites) {
|
|
|
|
for (let i = 0; i < rewrites.length; i++) {
|
|
|
|
let rule = rewrites[i];
|
|
|
|
if (summary.search(rule.pattern) != -1)
|
|
|
|
summary = summary.replace(rule.pattern, rule.replacement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-02 14:45:53 +00:00
|
|
|
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
|
|
|
|
|
|
|
|
let ndata = { appName: appName,
|
|
|
|
icon: icon,
|
|
|
|
summary: summary,
|
|
|
|
body: body,
|
|
|
|
actions: actions,
|
|
|
|
hints: hints,
|
|
|
|
timeout: timeout };
|
|
|
|
if (replacesId != 0 && this._notifications[replacesId]) {
|
|
|
|
ndata.id = id = replacesId;
|
|
|
|
ndata.notification = this._notifications[replacesId].notification;
|
|
|
|
} else {
|
|
|
|
replacesId = 0;
|
|
|
|
ndata.id = id = nextNotificationId++;
|
|
|
|
}
|
|
|
|
this._notifications[id] = ndata;
|
|
|
|
|
|
|
|
let sender = DBus.getCurrentMessageContext().sender;
|
|
|
|
let pid = this._senderToPid[sender];
|
2010-12-15 21:30:50 +00:00
|
|
|
|
|
|
|
let source = this._getSource(appName, pid, ndata);
|
2010-09-02 14:45:53 +00:00
|
|
|
|
|
|
|
if (source) {
|
|
|
|
this._notifyForSource(source, ndata);
|
|
|
|
return id;
|
2010-02-23 15:50:35 +00:00
|
|
|
}
|
|
|
|
|
2010-09-02 14:45:53 +00:00
|
|
|
if (replacesId) {
|
|
|
|
// There's already a pending call to GetConnectionUnixProcessID,
|
|
|
|
// which will see the new notification data when it finishes,
|
|
|
|
// so we don't have to do anything.
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._busProxy.GetConnectionUnixProcessIDRemote(sender, Lang.bind(this,
|
|
|
|
function (pid, ex) {
|
|
|
|
// The app may have updated or removed the notification
|
|
|
|
ndata = this._notifications[id];
|
|
|
|
if (!ndata)
|
|
|
|
return;
|
|
|
|
|
2010-12-15 21:30:50 +00:00
|
|
|
source = this._getSource(appName, pid, ndata);
|
|
|
|
|
|
|
|
// We only store sender-pid entries for persistent sources.
|
|
|
|
// 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,
|
|
|
|
function() {
|
|
|
|
delete this._senderToPid[sender];
|
|
|
|
}));
|
|
|
|
}
|
2010-09-02 14:45:53 +00:00
|
|
|
this._notifyForSource(source, ndata);
|
|
|
|
}));
|
|
|
|
|
|
|
|
return id;
|
|
|
|
},
|
|
|
|
|
|
|
|
_notifyForSource: function(source, ndata) {
|
|
|
|
let [id, icon, summary, body, actions, hints, notification] =
|
|
|
|
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
|
|
|
ndata.actions, ndata.hints, ndata.notification];
|
|
|
|
|
2010-08-05 17:09:27 +00:00
|
|
|
let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
|
2010-09-02 14:45:53 +00:00
|
|
|
|
2010-02-22 22:19:32 +00:00
|
|
|
if (notification == null) {
|
2010-11-23 23:31:55 +00:00
|
|
|
notification = new MessageTray.Notification(source, summary, body,
|
|
|
|
{ icon: iconActor,
|
|
|
|
bannerMarkup: true });
|
2010-09-02 14:45:53 +00:00
|
|
|
ndata.notification = notification;
|
MessageTray: untangle click notifications
Previously, when you clicked on a notification, it would call
this.source.clicked(), which would emit a 'clicked' signal on the
source, and then various other stuff would happen from there. This
used to make a little bit of sense, when clicking on a notification
was supposed to do the same thing as clicking on its source, but makes
less sense now, when clicking on the source itself *doesn't* call
source.clicked()...
Change it so that when you click on a notification, the notification
emits 'clicked' itself, and the source notices that and calls its
notificationClicked() method, and the various source subclasses do
what they need to do with that, and Source no longer has a clicked
method/signal.
https://bugzilla.gnome.org/show_bug.cgi?id=631042
2010-09-30 20:41:38 +00:00
|
|
|
notification.connect('clicked', Lang.bind(this,
|
2010-02-22 22:19:32 +00:00
|
|
|
function(n) {
|
2010-08-11 13:43:40 +00:00
|
|
|
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
|
2010-02-22 22:19:32 +00:00
|
|
|
}));
|
2010-07-14 21:07:06 +00:00
|
|
|
notification.connect('destroy', Lang.bind(this,
|
|
|
|
function(n) {
|
2010-09-02 14:45:53 +00:00
|
|
|
delete this._notifications[id];
|
2010-07-14 21:07:06 +00:00
|
|
|
}));
|
2010-12-16 20:49:47 +00:00
|
|
|
notification.connect('action-invoked', Lang.bind(this,
|
|
|
|
function(n, actionId) {
|
|
|
|
this._emitActionInvoked(id, actionId);
|
|
|
|
}));
|
2010-02-22 22:19:32 +00:00
|
|
|
} else {
|
2010-08-05 17:09:27 +00:00
|
|
|
notification.update(summary, body, { icon: iconActor,
|
2010-11-23 23:31:55 +00:00
|
|
|
bannerMarkup: true,
|
2010-08-05 17:09:27 +00:00
|
|
|
clear: true });
|
2010-02-22 22:19:32 +00:00
|
|
|
}
|
|
|
|
|
2010-02-01 20:41:22 +00:00
|
|
|
if (actions.length) {
|
2010-10-30 06:29:21 +00:00
|
|
|
notification.setUseActionIcons(hints['action-icons'] == true);
|
2010-02-01 20:41:22 +00:00
|
|
|
for (let i = 0; i < actions.length - 1; i += 2)
|
2010-02-22 19:23:36 +00:00
|
|
|
notification.addButton(actions[i], actions[i + 1]);
|
2010-02-01 20:41:22 +00:00
|
|
|
}
|
2011-01-04 09:34:57 +00:00
|
|
|
switch (hints.urgency) {
|
|
|
|
case Urgency.LOW:
|
|
|
|
notification.setUrgency(MessageTray.Urgency.LOW);
|
|
|
|
break;
|
|
|
|
case Urgency.NORMAL:
|
|
|
|
notification.setUrgency(MessageTray.Urgency.NORMAL);
|
|
|
|
break;
|
|
|
|
case Urgency.CRITICAL:
|
|
|
|
notification.setUrgency(MessageTray.Urgency.CRITICAL);
|
|
|
|
break;
|
|
|
|
}
|
2010-12-16 20:49:47 +00:00
|
|
|
notification.setResident(hints.resident == true);
|
2010-12-15 21:30:50 +00:00
|
|
|
// '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);
|
2010-04-28 19:34:27 +00:00
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
|
2010-09-02 14:45:53 +00:00
|
|
|
source.notify(notification, sourceIconActor);
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
CloseNotification: function(id) {
|
2010-09-02 14:45:53 +00:00
|
|
|
let ndata = this._notifications[id];
|
|
|
|
if (ndata) {
|
|
|
|
if (ndata.notification)
|
|
|
|
ndata.notification.destroy();
|
|
|
|
delete this._notifications[id];
|
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
this._emitNotificationClosed(id, NotificationClosedReason.APP_CLOSED);
|
|
|
|
},
|
|
|
|
|
|
|
|
GetCapabilities: function() {
|
|
|
|
return [
|
2010-02-01 20:41:22 +00:00
|
|
|
'actions',
|
2010-10-30 06:29:21 +00:00
|
|
|
'action-icons',
|
2010-01-13 20:05:20 +00:00
|
|
|
'body',
|
|
|
|
// 'body-hyperlinks',
|
|
|
|
// 'body-images',
|
|
|
|
'body-markup',
|
|
|
|
// 'icon-multi',
|
2010-06-11 21:19:18 +00:00
|
|
|
'icon-static',
|
2010-10-09 02:34:04 +00:00
|
|
|
'persistence',
|
2010-01-13 20:05:20 +00:00
|
|
|
// 'sound',
|
|
|
|
];
|
|
|
|
},
|
|
|
|
|
|
|
|
GetServerInformation: function() {
|
|
|
|
return [
|
|
|
|
'GNOME Shell',
|
|
|
|
'GNOME',
|
|
|
|
'0.1', // FIXME, get this from somewhere
|
|
|
|
'1.0'
|
|
|
|
];
|
|
|
|
},
|
|
|
|
|
2010-02-24 20:46:00 +00:00
|
|
|
_onFocusAppChanged: function() {
|
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
2010-08-09 17:18:15 +00:00
|
|
|
if (!tracker.focus_app)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (let id in this._sources) {
|
|
|
|
let source = this._sources[id];
|
|
|
|
if (source.app == tracker.focus_app) {
|
2010-12-16 20:49:47 +00:00
|
|
|
if (source.notification && !source.notification.resident)
|
|
|
|
source.notification.destroy();
|
2010-08-09 17:18:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-02-21 05:25:23 +00:00
|
|
|
},
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
_emitNotificationClosed: function(id, reason) {
|
|
|
|
DBus.session.emit_signal('/org/freedesktop/Notifications',
|
|
|
|
'org.freedesktop.Notifications',
|
|
|
|
'NotificationClosed', 'uu',
|
|
|
|
[id, reason]);
|
2010-02-01 20:41:22 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_emitActionInvoked: function(id, action) {
|
|
|
|
DBus.session.emit_signal('/org/freedesktop/Notifications',
|
|
|
|
'org.freedesktop.Notifications',
|
|
|
|
'ActionInvoked', 'us',
|
|
|
|
[id, action]);
|
2010-08-09 16:33:34 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onTrayIconAdded: function(o, icon) {
|
2010-12-15 21:30:50 +00:00
|
|
|
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null);
|
2010-08-09 16:33:34 +00:00
|
|
|
source.setTrayIcon(icon);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onTrayIconRemoved: function(o, icon) {
|
|
|
|
let source = this._sources[icon.pid];
|
|
|
|
if (source)
|
|
|
|
source.destroy();
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface);
|
|
|
|
|
2010-09-02 14:45:53 +00:00
|
|
|
function Source(title, pid) {
|
|
|
|
this._init(title, pid);
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Source.prototype = {
|
|
|
|
__proto__: MessageTray.Source.prototype,
|
|
|
|
|
2010-09-02 14:45:53 +00:00
|
|
|
_init: function(title, pid) {
|
2010-08-09 17:18:15 +00:00
|
|
|
MessageTray.Source.prototype._init.call(this, title);
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
this._pid = pid;
|
|
|
|
this._setApp();
|
|
|
|
if (this.app)
|
2010-09-02 14:45:53 +00:00
|
|
|
this.title = this.app.get_name();
|
2010-08-09 16:33:34 +00:00
|
|
|
else
|
|
|
|
this.useNotificationIcon = true;
|
2010-09-30 20:56:50 +00:00
|
|
|
this._isTrayIcon = false;
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-09-02 14:45:53 +00:00
|
|
|
notify: function(notification, icon) {
|
2010-08-09 16:33:34 +00:00
|
|
|
if (!this.app)
|
|
|
|
this._setApp();
|
|
|
|
if (!this.app && icon)
|
2010-09-02 14:45:53 +00:00
|
|
|
this._setSummaryIcon(icon);
|
2010-08-05 17:09:27 +00:00
|
|
|
MessageTray.Source.prototype.notify.call(this, notification);
|
2010-02-15 17:27:28 +00:00
|
|
|
},
|
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
_setApp: function() {
|
2010-12-16 20:49:47 +00:00
|
|
|
if (this.app)
|
|
|
|
return;
|
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid);
|
|
|
|
if (!this.app)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only override the icon if we were previously using
|
2011-01-07 15:22:28 +00:00
|
|
|
// notification-based icons (ie, not a trayicon) or if it was unset before
|
|
|
|
if (!this._isTrayIcon) {
|
2010-08-09 16:33:34 +00:00
|
|
|
this.useNotificationIcon = false;
|
|
|
|
this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
setTrayIcon: function(icon) {
|
|
|
|
this._setSummaryIcon(icon);
|
|
|
|
this.useNotificationIcon = false;
|
2010-09-30 20:56:50 +00:00
|
|
|
this._isTrayIcon = true;
|
2010-08-09 16:33:34 +00:00
|
|
|
},
|
|
|
|
|
2010-09-30 20:56:50 +00:00
|
|
|
_notificationClicked: function(notification) {
|
2010-02-15 17:27:28 +00:00
|
|
|
this.openApp();
|
2010-09-30 20:56:50 +00:00
|
|
|
},
|
|
|
|
|
2010-12-16 20:49:47 +00:00
|
|
|
_notificationRemoved: function() {
|
2010-09-30 20:56:50 +00:00
|
|
|
if (!this._isTrayIcon)
|
|
|
|
this.destroy();
|
2010-02-15 17:27:28 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
openApp: function() {
|
2010-09-02 14:45:53 +00:00
|
|
|
if (this.app == null)
|
2010-02-15 17:27:28 +00:00
|
|
|
return;
|
2010-09-02 14:45:53 +00:00
|
|
|
|
2010-02-15 17:27:28 +00:00
|
|
|
let windows = this.app.get_windows();
|
|
|
|
if (windows.length > 0) {
|
|
|
|
let mostRecentWindow = windows[0];
|
|
|
|
Main.activateWindow(mostRecentWindow);
|
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
|
|
|
};
|