Use Telepathy for IM notifications
And suppress libnotify-based notifications from Empathy https://bugzilla.gnome.org/show_bug.cgi?id=608999
This commit is contained in:
parent
5ab852bfa3
commit
5bce103a40
@ -3,4 +3,5 @@ jsmiscdir = $(pkgdatadir)/js/misc
|
|||||||
dist_jsmisc_DATA = \
|
dist_jsmisc_DATA = \
|
||||||
docInfo.js \
|
docInfo.js \
|
||||||
format.js \
|
format.js \
|
||||||
params.js
|
params.js \
|
||||||
|
telepathy.js
|
||||||
|
255
js/misc/telepathy.js
Normal file
255
js/misc/telepathy.js
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
|
const DBus = imports.dbus;
|
||||||
|
|
||||||
|
// D-Bus utils; should eventually move to gjs.
|
||||||
|
// https://bugzilla.gnome.org/show_bug.cgi?id=610859
|
||||||
|
|
||||||
|
function makeProxyClass(iface) {
|
||||||
|
let constructor = function() { this._init.apply(this, arguments); };
|
||||||
|
|
||||||
|
constructor.prototype._init = function(bus, name, path) {
|
||||||
|
bus.proxifyObject(this, name, path);
|
||||||
|
};
|
||||||
|
|
||||||
|
DBus.proxifyPrototype(constructor.prototype, iface);
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nameToPath(name) {
|
||||||
|
return '/' + name.replace('.', '/', 'g');
|
||||||
|
};
|
||||||
|
|
||||||
|
function pathToName(path) {
|
||||||
|
if (path[0] != '/')
|
||||||
|
throw new Error('not a D-Bus path: ' + path);
|
||||||
|
return path.substr(1).replace('/', '.', 'g');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Telepathy D-Bus interface definitions. Note that most of these are
|
||||||
|
// incomplete, and only cover the methods/properties/signals that
|
||||||
|
// we're currently using.
|
||||||
|
|
||||||
|
const TELEPATHY = 'org.freedesktop.Telepathy';
|
||||||
|
|
||||||
|
const CLIENT_NAME = TELEPATHY + '.Client';
|
||||||
|
const ClientIface = {
|
||||||
|
name: CLIENT_NAME,
|
||||||
|
properties: [
|
||||||
|
{ name: 'Interfaces',
|
||||||
|
signature: 'as',
|
||||||
|
access: 'read' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLIENT_APPROVER_NAME = TELEPATHY + '.Client.Approver';
|
||||||
|
const ClientApproverIface = {
|
||||||
|
name: CLIENT_APPROVER_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'AddDispatchOperation',
|
||||||
|
inSignature: 'a(oa{sv})oa{sv}',
|
||||||
|
outSignature: '' }
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{ name: 'ApproverChannelFilter',
|
||||||
|
signature: 'aa{sv}',
|
||||||
|
access: 'read' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLIENT_HANDLER_NAME = TELEPATHY + '.Client.Handler';
|
||||||
|
const ClientHandlerIface = {
|
||||||
|
name: CLIENT_HANDLER_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'HandleChannels',
|
||||||
|
inSignature: 'ooa(oa{sv})aota{sv}',
|
||||||
|
outSignature: '' }
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{ name: 'HandlerChannelFilter',
|
||||||
|
signature: 'aa{sv}',
|
||||||
|
access: 'read' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLIENT_OBSERVER_NAME = TELEPATHY + '.Client.Observer';
|
||||||
|
const ClientObserverIface = {
|
||||||
|
name: CLIENT_OBSERVER_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'ObserveChannels',
|
||||||
|
inSignature: 'ooa(oa{sv})oaoa{sv}',
|
||||||
|
outSignature: '' }
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{ name: 'ObserverChannelFilter',
|
||||||
|
signature: 'aa{sv}',
|
||||||
|
access: 'read' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const CHANNEL_DISPATCH_OPERATION_NAME = TELEPATHY + '.ChannelDispatchOperation';
|
||||||
|
const ChannelDispatchOperationIface = {
|
||||||
|
name: CHANNEL_DISPATCH_OPERATION_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'HandleWith',
|
||||||
|
inSignature: 's',
|
||||||
|
outSignature: '' },
|
||||||
|
{ name: 'Claim',
|
||||||
|
inSignature: '',
|
||||||
|
outSignature: '' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let ChannelDispatchOperation = makeProxyClass(ChannelDispatchOperationIface);
|
||||||
|
|
||||||
|
const CONNECTION_NAME = TELEPATHY + '.Connection';
|
||||||
|
const ConnectionIface = {
|
||||||
|
name: CONNECTION_NAME,
|
||||||
|
signals: [
|
||||||
|
{ name: 'StatusChanged',
|
||||||
|
inSignature: 'uu' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let Connection = makeProxyClass(ConnectionIface);
|
||||||
|
|
||||||
|
const ConnectionStatus = {
|
||||||
|
CONNECTED: 0,
|
||||||
|
CONNECTING: 1,
|
||||||
|
DISCONNECTED: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
const CONNECTION_ALIASING_NAME = CONNECTION_NAME + '.Interface.Aliasing';
|
||||||
|
const ConnectionAliasingIface = {
|
||||||
|
name: CONNECTION_ALIASING_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'RequestAliases',
|
||||||
|
inSignature: 'au',
|
||||||
|
outSignature: 'as'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
signals: [
|
||||||
|
{ name: 'AliasesChanged',
|
||||||
|
inSignature: 'a(us)' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let ConnectionAliasing = makeProxyClass(ConnectionAliasingIface);
|
||||||
|
|
||||||
|
const CONNECTION_AVATARS_NAME = CONNECTION_NAME + '.Interface.Avatars';
|
||||||
|
const ConnectionAvatarsIface = {
|
||||||
|
name: CONNECTION_AVATARS_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'GetKnownAvatarTokens',
|
||||||
|
inSignature: 'au',
|
||||||
|
outSignature: 'a{us}'
|
||||||
|
},
|
||||||
|
{ name: 'RequestAvatars',
|
||||||
|
inSignature: 'au',
|
||||||
|
outSignature: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
signals: [
|
||||||
|
{ name: 'AvatarRetrieved',
|
||||||
|
inSignature: 'usays'
|
||||||
|
},
|
||||||
|
{ name: 'AvatarUpdated',
|
||||||
|
inSignature: 'us'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let ConnectionAvatars = makeProxyClass(ConnectionAvatarsIface);
|
||||||
|
|
||||||
|
const CONNECTION_REQUESTS_NAME = CONNECTION_NAME + '.Interface.Requests';
|
||||||
|
const ConnectionRequestsIface = {
|
||||||
|
name: CONNECTION_REQUESTS_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'CreateChannel',
|
||||||
|
inSignature: 'a{sv}',
|
||||||
|
outSignature: 'oa{sv}'
|
||||||
|
},
|
||||||
|
{ name: 'EnsureChannel',
|
||||||
|
inSignature: 'a{sv}',
|
||||||
|
outSignature: 'boa{sv}'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{ name: 'Channels',
|
||||||
|
signature: 'a(oa{sv})',
|
||||||
|
access: 'read' }
|
||||||
|
],
|
||||||
|
signals: [
|
||||||
|
{ name: 'NewChannels',
|
||||||
|
inSignature: 'a(oa{sv})'
|
||||||
|
},
|
||||||
|
{ name: 'ChannelClosed',
|
||||||
|
inSignature: 'o'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let ConnectionRequests = makeProxyClass(ConnectionRequestsIface);
|
||||||
|
|
||||||
|
const HandleType = {
|
||||||
|
NONE: 0,
|
||||||
|
CONTACT: 1,
|
||||||
|
ROOM: 2,
|
||||||
|
LIST: 3,
|
||||||
|
GROUP: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
const CHANNEL_NAME = TELEPATHY + '.Channel';
|
||||||
|
const ChannelIface = {
|
||||||
|
name: CHANNEL_NAME,
|
||||||
|
signals: [
|
||||||
|
{ name: 'Closed',
|
||||||
|
inSignature: '' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let Channel = makeProxyClass(ChannelIface);
|
||||||
|
|
||||||
|
const CHANNEL_TEXT_NAME = CHANNEL_NAME + '.Type.Text';
|
||||||
|
const ChannelTextIface = {
|
||||||
|
name: CHANNEL_TEXT_NAME,
|
||||||
|
methods: [
|
||||||
|
{ name: 'ListPendingMessages',
|
||||||
|
inSignature: 'b',
|
||||||
|
outSignature: 'a(uuuuus)'
|
||||||
|
},
|
||||||
|
{ name: 'AcknowledgePendingMessages',
|
||||||
|
inSignature: 'au',
|
||||||
|
outSignature: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
signals: [
|
||||||
|
{ name: 'Received',
|
||||||
|
inSignature: 'uuuuus' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let ChannelText = makeProxyClass(ChannelTextIface);
|
||||||
|
|
||||||
|
const ChannelTextMessageType = {
|
||||||
|
NORMAL: 0,
|
||||||
|
ACTION: 1,
|
||||||
|
NOTICE: 2,
|
||||||
|
AUTO_REPLY: 3,
|
||||||
|
DELIVERY_REPORT: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
const ACCOUNT_MANAGER_NAME = TELEPATHY + '.AccountManager';
|
||||||
|
const AccountManagerIface = {
|
||||||
|
name: ACCOUNT_MANAGER_NAME,
|
||||||
|
properties: [
|
||||||
|
{ name: 'ValidAccounts',
|
||||||
|
signature: 'ao',
|
||||||
|
access: 'read' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let AccountManager = makeProxyClass(AccountManagerIface);
|
||||||
|
|
||||||
|
const ACCOUNT_NAME = TELEPATHY + '.Account';
|
||||||
|
const AccountIface = {
|
||||||
|
name: ACCOUNT_NAME,
|
||||||
|
properties: [
|
||||||
|
{ name: 'Connection',
|
||||||
|
signature: 'o',
|
||||||
|
access: 'read' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let Account = makeProxyClass(AccountIface);
|
@ -25,6 +25,7 @@ dist_jsui_DATA = \
|
|||||||
search.js \
|
search.js \
|
||||||
shellDBus.js \
|
shellDBus.js \
|
||||||
statusMenu.js \
|
statusMenu.js \
|
||||||
|
telepathyClient.js \
|
||||||
tweener.js \
|
tweener.js \
|
||||||
windowAttentionHandler.js \
|
windowAttentionHandler.js \
|
||||||
windowManager.js \
|
windowManager.js \
|
||||||
|
@ -24,6 +24,7 @@ const LookingGlass = imports.ui.lookingGlass;
|
|||||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||||
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
||||||
const ShellDBus = imports.ui.shellDBus;
|
const ShellDBus = imports.ui.shellDBus;
|
||||||
|
const TelepathyClient = imports.ui.telepathyClient;
|
||||||
const WindowManager = imports.ui.windowManager;
|
const WindowManager = imports.ui.windowManager;
|
||||||
|
|
||||||
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
||||||
@ -36,9 +37,10 @@ let overview = null;
|
|||||||
let runDialog = null;
|
let runDialog = null;
|
||||||
let lookingGlass = null;
|
let lookingGlass = null;
|
||||||
let wm = null;
|
let wm = null;
|
||||||
let notificationDaemon = null;
|
|
||||||
let messageTray = null;
|
let messageTray = null;
|
||||||
|
let notificationDaemon = null;
|
||||||
let windowAttentionHandler = null;
|
let windowAttentionHandler = null;
|
||||||
|
let telepathyClient = null;
|
||||||
let recorder = null;
|
let recorder = null;
|
||||||
let shellDBusService = null;
|
let shellDBusService = null;
|
||||||
let modalCount = 0;
|
let modalCount = 0;
|
||||||
@ -108,9 +110,10 @@ function start() {
|
|||||||
chrome = new Chrome.Chrome();
|
chrome = new Chrome.Chrome();
|
||||||
panel = new Panel.Panel();
|
panel = new Panel.Panel();
|
||||||
wm = new WindowManager.WindowManager();
|
wm = new WindowManager.WindowManager();
|
||||||
|
messageTray = new MessageTray.MessageTray();
|
||||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||||
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
||||||
messageTray = new MessageTray.MessageTray();
|
telepathyClient = new TelepathyClient.Client();
|
||||||
|
|
||||||
_startDate = new Date();
|
_startDate = new Date();
|
||||||
|
|
||||||
|
@ -140,6 +140,17 @@ NotificationDaemon.prototype = {
|
|||||||
let source = Main.messageTray.getSource(this._sourceId(appName));
|
let source = Main.messageTray.getSource(this._sourceId(appName));
|
||||||
let id = null;
|
let id = null;
|
||||||
|
|
||||||
|
// Filter out notifications from Empathy, since we
|
||||||
|
// handle that information from telepathyClient.js
|
||||||
|
if (appName == 'Empathy') {
|
||||||
|
id = nextNotificationId++;
|
||||||
|
Mainloop.idle_add(Lang.bind(this,
|
||||||
|
function () {
|
||||||
|
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
|
||||||
|
}));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
// Source may be null if we have never received a notification from
|
// Source may be null if we have never received a notification from
|
||||||
// this app or if all notifications from this app have been acknowledged.
|
// this app or if all notifications from this app have been acknowledged.
|
||||||
if (source == null) {
|
if (source == null) {
|
||||||
|
364
js/ui/telepathyClient.js
Normal file
364
js/ui/telepathyClient.js
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
|
const DBus = imports.dbus;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
const Telepathy = imports.misc.telepathy;
|
||||||
|
|
||||||
|
let avatarManager;
|
||||||
|
|
||||||
|
// This is GNOME Shell's implementation of the Telepathy "Client"
|
||||||
|
// interface. Specifically, the shell is a Telepathy "Approver", which
|
||||||
|
// lets us control the routing of incoming messages, a "Handler",
|
||||||
|
// which lets us receive and respond to messages, and an "Observer",
|
||||||
|
// which lets us see messages even if they belong to another app (eg,
|
||||||
|
// a conversation started from within Empathy).
|
||||||
|
|
||||||
|
function Client() {
|
||||||
|
this._init();
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.prototype = {
|
||||||
|
_init : function() {
|
||||||
|
let name = Telepathy.CLIENT_NAME + '.GnomeShell';
|
||||||
|
DBus.session.exportObject(Telepathy.nameToPath(name), this);
|
||||||
|
DBus.session.acquire_name(name, DBus.SINGLE_INSTANCE,
|
||||||
|
function (name) { /* FIXME: acquired */ },
|
||||||
|
function (name) { /* FIXME: lost */ });
|
||||||
|
|
||||||
|
this._channels = {};
|
||||||
|
|
||||||
|
avatarManager = new AvatarManager();
|
||||||
|
|
||||||
|
// Acquire existing connections. (Needed to make things work
|
||||||
|
// through a restart.)
|
||||||
|
let accountManager = new Telepathy.AccountManager(DBus.session,
|
||||||
|
Telepathy.ACCOUNT_MANAGER_NAME,
|
||||||
|
Telepathy.nameToPath(Telepathy.ACCOUNT_MANAGER_NAME));
|
||||||
|
accountManager.GetRemote('ValidAccounts', Lang.bind(this, this._gotValidAccounts));
|
||||||
|
},
|
||||||
|
|
||||||
|
_gotValidAccounts: function(accounts, err) {
|
||||||
|
if (!accounts)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let i = 0; i < accounts.length; i++) {
|
||||||
|
let account = new Telepathy.Account(DBus.session,
|
||||||
|
Telepathy.ACCOUNT_MANAGER_NAME,
|
||||||
|
accounts[i]);
|
||||||
|
account.GetRemote('Connection', Lang.bind(this,
|
||||||
|
function (connPath, err) {
|
||||||
|
if (!connPath || connPath == '/')
|
||||||
|
return;
|
||||||
|
|
||||||
|
let connReq = new Telepathy.ConnectionRequests(DBus.session,
|
||||||
|
Telepathy.pathToName(connPath),
|
||||||
|
connPath);
|
||||||
|
connReq.GetRemote('Channels', Lang.bind(this,
|
||||||
|
function(channels, err) {
|
||||||
|
if (!channels)
|
||||||
|
return;
|
||||||
|
this._addChannels(connPath, channels);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get Interfaces() {
|
||||||
|
return [ Telepathy.CLIENT_APPROVER_NAME,
|
||||||
|
Telepathy.CLIENT_HANDLER_NAME,
|
||||||
|
Telepathy.CLIENT_OBSERVER_NAME ];
|
||||||
|
},
|
||||||
|
|
||||||
|
get ApproverChannelFilter() {
|
||||||
|
return [
|
||||||
|
{ 'org.freedesktop.Telepathy.Channel.ChannelType': Telepathy.CHANNEL_TEXT_NAME }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
AddDispatchOperation: function(channels, dispatchOperationPath, properties) {
|
||||||
|
let sender = DBus.getCurrentMessageContext().sender;
|
||||||
|
let op = new Telepathy.ChannelDispatchOperation(DBus.session, sender,
|
||||||
|
dispatchOperationPath);
|
||||||
|
op.ClaimRemote();
|
||||||
|
},
|
||||||
|
|
||||||
|
get HandlerChannelFilter() {
|
||||||
|
return [
|
||||||
|
{ 'org.freedesktop.Telepathy.Channel.ChannelType': Telepathy.CHANNEL_TEXT_NAME }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
HandleChannels: function(account, connPath, channels,
|
||||||
|
requestsSatisfied, userActionTime,
|
||||||
|
handlerInfo) {
|
||||||
|
this._addChannels(connPath, channels);
|
||||||
|
},
|
||||||
|
|
||||||
|
get ObserverChannelFilter() {
|
||||||
|
return [
|
||||||
|
{ 'org.freedesktop.Telepathy.Channel.ChannelType': Telepathy.CHANNEL_TEXT_NAME }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
ObserveChannels: function(account, connPath, channels,
|
||||||
|
dispatchOperation, requestsSatisfied,
|
||||||
|
observerInfo) {
|
||||||
|
this._addChannels(connPath, channels);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addChannels: function(connPath, channelDetailsList) {
|
||||||
|
for (let i = 0; i < channelDetailsList.length; i++) {
|
||||||
|
let [channelPath, props] = channelDetailsList[i];
|
||||||
|
if (this._channels[channelPath])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let channelType = props[Telepathy.CHANNEL_NAME + '.ChannelType'];
|
||||||
|
if (channelType != Telepathy.CHANNEL_TEXT_NAME)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let targetHandle = props[Telepathy.CHANNEL_NAME + '.TargetHandle'];
|
||||||
|
let targetHandleType = props[Telepathy.CHANNEL_NAME + '.TargetHandleType'];
|
||||||
|
let targetId = props[Telepathy.CHANNEL_NAME + '.TargetID'];
|
||||||
|
|
||||||
|
let source = new Source(connPath, channelPath,
|
||||||
|
targetHandle, targetHandleType, targetId);
|
||||||
|
this._channels[channelPath] = source;
|
||||||
|
source.connect('destroy', Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
delete this._channels[channelPath];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DBus.conformExport(Client.prototype, Telepathy.ClientIface);
|
||||||
|
DBus.conformExport(Client.prototype, Telepathy.ClientApproverIface);
|
||||||
|
DBus.conformExport(Client.prototype, Telepathy.ClientHandlerIface);
|
||||||
|
DBus.conformExport(Client.prototype, Telepathy.ClientObserverIface);
|
||||||
|
|
||||||
|
|
||||||
|
function AvatarManager() {
|
||||||
|
this._init();
|
||||||
|
};
|
||||||
|
|
||||||
|
AvatarManager.prototype = {
|
||||||
|
_init: function() {
|
||||||
|
this._connections = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
_addConnection: function(conn) {
|
||||||
|
if (this._connections[conn.getPath()])
|
||||||
|
return this._connections[conn.getPath()];
|
||||||
|
|
||||||
|
let info = {};
|
||||||
|
|
||||||
|
// avatarData[handle] describes the icon for @handle:
|
||||||
|
// either the string 'default', meaning to use the default
|
||||||
|
// avatar, or an array of bytes containing, eg, PNG data.
|
||||||
|
info.avatarData = {};
|
||||||
|
|
||||||
|
// icons[handle] is an array of the icon actors currently
|
||||||
|
// being displayed for @handle. These will be updated
|
||||||
|
// automatically if @handle's avatar changes.
|
||||||
|
info.icons = {};
|
||||||
|
|
||||||
|
info.connectionAvatars = new Telepathy.ConnectionAvatars(DBus.session,
|
||||||
|
conn.getBusName(),
|
||||||
|
conn.getPath());
|
||||||
|
info.updatedId = info.connectionAvatars.connect(
|
||||||
|
'AvatarUpdated', Lang.bind(this, this._avatarUpdated));
|
||||||
|
info.retrievedId = info.connectionAvatars.connect(
|
||||||
|
'AvatarRetrieved', Lang.bind(this, this._avatarRetrieved));
|
||||||
|
|
||||||
|
info.statusChangedId = conn.connect('StatusChanged', Lang.bind(this,
|
||||||
|
function (status, reason) {
|
||||||
|
if (status == Telepathy.ConnectionStatus.DISCONNECTED)
|
||||||
|
this._removeConnection(conn);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._connections[conn.getPath()] = info;
|
||||||
|
return info;
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeConnection: function(conn) {
|
||||||
|
let info = this._connections[conn.getPath()];
|
||||||
|
if (!info)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conn.disconnect(info.statusChangedId);
|
||||||
|
info.connectionAvatars.disconnect(info.updatedId);
|
||||||
|
info.connectionAvatars.disconnect(info.retrievedId);
|
||||||
|
|
||||||
|
delete this._connections[conn.getPath()];
|
||||||
|
},
|
||||||
|
|
||||||
|
_avatarUpdated: function(conn, handle, token) {
|
||||||
|
let info = this._connections[conn.getPath()];
|
||||||
|
if (!info)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!info.avatarData[handle]) {
|
||||||
|
// This would only happen if either (a) the initial
|
||||||
|
// RequestAvatars() call hasn't returned yet, or (b)
|
||||||
|
// Telepathy is informing us about avatars we didn't ask
|
||||||
|
// about. Either way, we don't have to do anything here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == '') {
|
||||||
|
// Invoke the next async callback in the chain, telling
|
||||||
|
// it to use the default image.
|
||||||
|
this._avatarRetrieved(conn, handle, token, 'default', null);
|
||||||
|
} else {
|
||||||
|
// In this case, @token is some sort of UUID. Telepathy
|
||||||
|
// expects us to cache avatar images to disk and use the
|
||||||
|
// tokens to figure out when we already have the right
|
||||||
|
// images cached. But we don't do that, we just
|
||||||
|
// ignore @token and request the image unconditionally.
|
||||||
|
info.connectionAvatars.RequestAvatarsRemote([handle]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_createIcon: function(iconData, size) {
|
||||||
|
let textureCache = St.TextureCache.get_default();
|
||||||
|
if (iconData == 'default')
|
||||||
|
return textureCache.load_icon_name('stock_person', size);
|
||||||
|
else
|
||||||
|
return textureCache.load_from_data(iconData, iconData.length, size);
|
||||||
|
},
|
||||||
|
|
||||||
|
_avatarRetrieved: function(conn, handle, token, avatarData, mimeType) {
|
||||||
|
let info = this._connections[conn.getPath()];
|
||||||
|
if (!info)
|
||||||
|
return;
|
||||||
|
|
||||||
|
info.avatarData[handle] = avatarData;
|
||||||
|
if (!info.icons[handle])
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let i = 0; i < info.icons[handle].length; i++) {
|
||||||
|
let iconBox = info.icons[handle][i];
|
||||||
|
let size = iconBox.child.height;
|
||||||
|
iconBox.child = this._createIcon(avatarData, size);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createAvatar: function(conn, handle, size) {
|
||||||
|
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
||||||
|
|
||||||
|
let info = this._connections[conn.getPath()];
|
||||||
|
if (!info)
|
||||||
|
info = this._addConnection(conn);
|
||||||
|
|
||||||
|
if (!info.icons[handle])
|
||||||
|
info.icons[handle] = [];
|
||||||
|
info.icons[handle].push(iconBox);
|
||||||
|
|
||||||
|
iconBox.connect('destroy', Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
let i = info.icons[handle].indexOf(iconBox);
|
||||||
|
if (i != -1)
|
||||||
|
info.icons[handle].splice(i, 1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let avatarData = info.avatarData[handle];
|
||||||
|
if (avatarData) {
|
||||||
|
iconBox.child = this._createIcon(avatarData, size);
|
||||||
|
return iconBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the default icon and then asynchronously load
|
||||||
|
// the real avatar.
|
||||||
|
iconBox.child = this._createIcon('default', size);
|
||||||
|
info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
|
||||||
|
function (tokens, err) {
|
||||||
|
if (tokens && tokens[handle])
|
||||||
|
info.connectionAvatars.RequestAvatarsRemote([handle]);
|
||||||
|
else
|
||||||
|
info.avatarData[handle] = 'default';
|
||||||
|
}));
|
||||||
|
|
||||||
|
return iconBox;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function Source(connPath, channelPath, targetHandle, targetHandleType, targetId) {
|
||||||
|
this._init(connPath, channelPath, targetHandle, targetHandleType, targetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Source.prototype = {
|
||||||
|
__proto__: MessageTray.Source.prototype,
|
||||||
|
|
||||||
|
_init: function(connPath, channelPath, targetHandle, targetHandleType, targetId) {
|
||||||
|
MessageTray.Source.prototype._init.call(this, targetId);
|
||||||
|
|
||||||
|
let connName = Telepathy.pathToName(connPath);
|
||||||
|
this._conn = new Telepathy.Connection(DBus.session, connName, connPath);
|
||||||
|
this._channel = new Telepathy.Channel(DBus.session, connName, channelPath);
|
||||||
|
this._closedId = this._channel.connect('Closed', Lang.bind(this, this._channelClosed));
|
||||||
|
|
||||||
|
this._targetHandle = targetHandle;
|
||||||
|
this._targetId = targetId;
|
||||||
|
|
||||||
|
this.name = this._targetId;
|
||||||
|
if (targetHandleType == Telepathy.HandleType.CONTACT) {
|
||||||
|
let aliasing = new Telepathy.ConnectionAliasing(DBus.session, connName, connPath);
|
||||||
|
aliasing.RequestAliasesRemote([this._targetHandle], Lang.bind(this,
|
||||||
|
function (aliases, err) {
|
||||||
|
if (aliases && aliases.length)
|
||||||
|
this.name = aliases[0];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._channelText = new Telepathy.ChannelText(DBus.session, connName, channelPath);
|
||||||
|
this._receivedId = this._channelText.connect('Received', Lang.bind(this, this._receivedMessage));
|
||||||
|
|
||||||
|
this._channelText.ListPendingMessagesRemote(false, Lang.bind(this, this._gotPendingMessages));
|
||||||
|
},
|
||||||
|
|
||||||
|
createIcon: function(size) {
|
||||||
|
return avatarManager.createAvatar(this._conn, this._targetHandle, size);
|
||||||
|
},
|
||||||
|
|
||||||
|
_gotPendingMessages: function(msgs, err) {
|
||||||
|
if (!msgs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let i = 0; i < msgs.length; i++)
|
||||||
|
this._receivedMessage.apply(this, [this._channel].concat(msgs[i]));
|
||||||
|
},
|
||||||
|
|
||||||
|
_channelClosed: function() {
|
||||||
|
this._channel.disconnect(this._closedId);
|
||||||
|
this._channelText.disconnect(this._receivedId);
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_receivedMessage: function(channel, id, timestamp, sender,
|
||||||
|
type, flags, text) {
|
||||||
|
if (!Main.messageTray.contains(this))
|
||||||
|
Main.messageTray.add(this);
|
||||||
|
|
||||||
|
let notification = new Notification(this._targetId, this, text);
|
||||||
|
this.notify(notification);
|
||||||
|
|
||||||
|
this._channelText.AcknowledgePendingMessagesRemote([id]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function Notification(id, source, text) {
|
||||||
|
this._init(id, source, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification.prototype = {
|
||||||
|
__proto__: MessageTray.Notification.prototype,
|
||||||
|
|
||||||
|
_init: function(id, source, text) {
|
||||||
|
MessageTray.Notification.prototype._init.call(this, id, source, source.name, text, true);
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user