1 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
4 * Pidgin GnomeShell Integration.
6 * Credits to the author of Gajim extension as this extension code was modified
11 const DBus = imports.dbus;
12 const GLib = imports.gi.GLib;
13 const Lang = imports.lang;
14 const St = imports.gi.St;
15 const Main = imports.ui.main;
16 const MessageTray = imports.ui.messageTray;
17 const Shell = imports.gi.Shell;
18 const TelepathyClient = imports.ui.telepathyClient;
19 const Tp = imports.gi.TelepathyGLib;
21 const Gettext = imports.gettext.domain('gnome-shell-extensions');
22 const _ = Gettext.gettext;
24 function wrappedText(text, sender, timestamp, direction, chat) {
25 let currentTime = (Date.now() / 1000);
26 let type = Tp.ChannelTextMessageType.NORMAL;
28 if (timestamp == null) {
29 timestamp = currentTime;
32 text = _fixText(text);
33 if (chat && direction != TelepathyClient.NotificationDirection.SENT){
34 text = sender + ": " + text;
36 if (text.substr(0, 3) == '/me' && direction != TelepathyClient.NotificationDirection.SENT) {
37 text = text.substr(4);
38 type = Tp.ChannelTextMessageType.ACTION;
51 function PidginNotification(source) {
55 function _fixText(text) {
57 let _text = text.replace(/<\/?[^>]+(>|$)/g, "");
61 PidginNotification.prototype = {
62 __proto__: TelepathyClient.ChatNotification.prototype
65 function PidginChatNotification(source) {
69 PidginChatNotification.prototype = {
70 __proto__: TelepathyClient.ChatNotification.prototype
73 function Source(client, account, author, initialMessage, conversation, chat, flag) {
74 this._init(client, account, author, initialMessage, conversation, chat, flag);
78 __proto__: MessageTray.Source.prototype,
80 _init: function(client, account, author, initialMessage, conversation, chat, flag) {
82 let proxy = client.proxy();
83 this._client = client;
84 this._author = author;
86 this._authors[author] = true;
87 this._account = account;
88 this._conversation = conversation;
90 this._initialMessage = initialMessage;
91 this._initialFlag = flag;
93 this._presence = 'online';
96 this._notification = new PidginChatNotification(this);
98 this._notification = new PidginNotification(this);
100 this._notification.setUrgency(MessageTray.Urgency.HIGH);
101 this._notification.enableScrolling(true);
103 proxy.PurpleConversationGetTitleRemote(this._conversation, Lang.bind(this, this._async_set_title));
104 if (chat) proxy.PurpleConvChatRemote(this._conversation, Lang.bind(this, this._async_set_conversation_im));
107 _async_set_author_buddy: function (author_buddy) {
108 let proxy = this._client.proxy();
109 this._author_buddy = author_buddy;
110 proxy.PurpleConvImRemote(this._conversation, Lang.bind(this, this._async_set_conversation_im));
113 _async_set_conversation_im: function (conversation_im) {
114 let proxy = this._client.proxy();
115 this._conversation_im = conversation_im;
116 proxy.PurpleBuddyGetIconRemote(this._author_buddy, Lang.bind(this, this._async_get_icon));
119 _async_set_title: function (title) {
120 let proxy = this._client.proxy();
121 this.title = _fixText(title);
123 proxy.PurpleFindBuddyRemote(this._account, this._author, Lang.bind(this, this._async_set_author_buddy))
129 _async_get_icon: function (iconobj) {
130 let proxy = this._client.proxy();
132 proxy.PurpleBuddyIconGetFullPathRemote(iconobj, Lang.bind(this, this._async_set_icon));
138 _async_set_icon: function (iconpath) {
139 this._iconUri = 'file://' + iconpath;
143 _start: function () {
145 let proxy = this._client.proxy();
146 MessageTray.Source.prototype._init.call(this, this.title);
148 this._setSummaryIcon(this.createNotificationIcon());
150 Main.messageTray.add(this);
151 this.pushNotification(this._notification);
153 let direction = null;
154 if (this._initialFlag == 1) {
155 direction = TelepathyClient.NotificationDirection.SENT;
156 } else if (this._initialFlag == 2) {
157 direction = TelepathyClient.NotificationDirection.RECEIVED;
160 let message = wrappedText(this._initialMessage, this._author, null, direction, this._chat);
161 this._notification.appendMessage(message, false);
164 this._messageDisplayedId = proxy.connect('DisplayedChatMsg', Lang.bind(this, this._onDisplayedChatMessage));
166 this._buddyStatusChangeId = proxy.connect('BuddyStatusChanged', Lang.bind(this, this._onBuddyStatusChange));
167 this._buddySignedOffId = proxy.connect('BuddySignedOff', Lang.bind(this, this._onBuddySignedOff));
168 this._buddySignedOnId = proxy.connect('BuddySignedOn', Lang.bind(this, this._onBuddySignedOn));
169 this._messageDisplayedId = proxy.connect('DisplayedImMsg', Lang.bind(this, this._onDisplayedImMessage));
171 this._deleteConversationId = proxy.connect('DeletingConversation', Lang.bind(this, this._onDeleteConversation));
176 destroy: function () {
177 let proxy = this._client.proxy();
178 proxy.disconnect(this._buddyStatusChangeId);
179 proxy.disconnect(this._buddySignedOffId);
180 proxy.disconnect(this._buddySignedOnId);
181 proxy.disconnect(this._deleteConversationId);
182 proxy.disconnect(this._messageDisplayedId);
183 MessageTray.Source.prototype.destroy.call(this);
187 createNotificationIcon: function() {
188 let iconBox = new St.Bin({ style_class: 'avatar-box' });
189 iconBox._size = this.ICON_SIZE;
191 if (!this._iconUri) {
192 iconBox.child = new St.Icon({ icon_name: 'avatar-default',
193 icon_type: St.IconType.FULLCOLOR,
194 icon_size: iconBox._size });
196 let textureCache = St.TextureCache.get_default();
197 iconBox.child = textureCache.load_uri_async(this._iconUri, iconBox._size, iconBox._size);
202 open: function(notification) {
203 let app = Shell.AppSystem.get_default().get_app('pidgin.desktop');
204 app.activate_window(null, global.get_current_time());
210 notify: function () {
211 let proxy = this._client.proxy();
212 proxy.PurpleConversationHasFocusRemote(this._conversation, Lang.bind(this, this._async_notify));
215 _async_notify: function (stats) {
217 MessageTray.Source.prototype.notify.call(this, this._notification);
218 this._notification.scrollTo(St.Side.BOTTOM);
222 respond: function(text) {
223 let proxy = this._client.proxy();
224 let _text = GLib.markup_escape_text(text, -1);
226 proxy.PurpleConvChatSendRemote(this._conversation_im, _text);
229 proxy.PurpleConvImSendRemote(this._conversation_im, _text);
233 _onBuddyStatusChange: function (emitter, buddy, old_status_id, new_status_id) {
234 if (!this.title) return;
238 let proxy = this._client.proxy();
240 if (buddy != this._author_buddy) return;
242 let presenceInfo = {};
244 let notify_presence = function () {
246 let presence = presenceInfo.presence;
247 let message = presenceInfo.message;
248 if (self._presence == presence) return;
250 let title = self.title;
251 let presenceMessage, shouldNotify;
252 if (presence == "away") {
253 presenceMessage = _("%s is away.").format(title);
254 } else if (presence == "available") {
255 presenceMessage = _("%s is available.").format(title);
256 } else if (presence == "dnd") {
257 presenceMessage = _("%s is busy.").format(title);
262 self._presence = presence;
265 presenceMessage += ' <i>(' + _fixText(message) + ')</i>';
267 self._notification.appendPresence(presenceMessage, false);
270 let set_presence_message = function (message) {
271 presenceInfo.message = message;
274 let set_presence = function (presence) {
275 presenceInfo.presence = presence;
276 proxy.PurpleStatusGetAttrStringRemote(new_status_id, 'message', set_presence_message);
279 proxy.PurpleStatusGetIdRemote(new_status_id, set_presence);
283 _onBuddySignedOff: function(emitter, buddy) {
284 if (buddy != this._author_buddy) return;
286 let shouldNotify = this._presence != 'offline';
287 let presenceMessage = _("%s is offline.").format(this.title);
288 this._notification.appendPresence(presenceMessage, shouldNotify);
289 this._presence = 'offline';
294 _onBuddySignedOn: function(emitter, buddy) {
295 if (buddy != this._author_buddy) return;
297 let shouldNotify = this._presence == 'offline';
298 let presenceMessage = _("%s is online.").format(this.title);
299 this._notification.appendPresence(presenceMessage, shouldNotify);
300 this._presence = 'online';
305 _onDeleteConversation: function(emitter, conversation) {
306 if (conversation != this._conversation) return;
310 _onDisplayedChatMessage: function(emitter, account, author, text, conversation, flag) {
312 if (text && (this._conversation == conversation) && (flag & 3) == 2) {
313 // accept messages from people who sent us something with our nick in it
314 if ((flag & 32) == 32) {
315 this._authors[author] = true;
317 if (author in this._authors) {
318 let message = wrappedText(text, author, null, TelepathyClient.NotificationDirection.RECEIVED, this._chat);
319 this._notification.appendMessage(message, false);
324 let message = wrappedText(text, author, null, TelepathyClient.NotificationDirection.SENT, this._chat);
325 this._notification.appendMessage(message, false);
331 _onDisplayedImMessage: function(emitter, account, author, text, conversation, flag) {
333 if (text && (this._conversation == conversation)) {
334 let direction = null;
336 direction = TelepathyClient.NotificationDirection.SENT;
337 } else if (flag == 2) {
338 direction = TelepathyClient.NotificationDirection.RECEIVED;
340 if (direction != null) {
341 let message = wrappedText(text, author, null, direction);
342 this._notification.appendMessage(message, false);
345 if (direction == TelepathyClient.NotificationDirection.RECEIVED) {
354 const PidginIface = {
355 name: 'im.pidgin.purple.PurpleInterface',
358 {name: 'PurpleGetIms', inSignature: '', outSignature: 'ai'},
359 {name: 'PurpleAccountsGetAllActive', inSignature: '', outSignature: 'ai'},
360 {name: 'PurpleConversationGetType', inSignature: 'i', outSignature: 'u'},
361 {name: 'PurpleFindBuddies', inSignature: 'is', outSignature: 'ai'},
362 {name: 'PurpleFindBuddy', inSignature: 'is', outSignature: 'i'},
363 {name: 'PurpleAccountGetAlias', inSignature: 'i', outSignature: 's'},
364 {name: 'PurpleAccountGetNameForDisplay', inSignature: 'i', outSignature: 's'},
365 {name: 'PurpleBuddyGetAlias', inSignature: 'i', outSignature: 's'},
366 {name: 'PurpleBuddyGetName', inSignature: 'i', outSignature: 's'},
367 {name: 'PurpleStatusGetId', inSignature: 'i', outSignature: 's'},
368 {name: 'PurpleStatusGetAttrString', inSignature: 'is', outSignature: 's'},
369 {name: 'PurpleBuddyIconGetFullPath', inSignature: 'i', outSignature: 's'},
370 {name: 'PurpleBuddyGetIcon', inSignature: 'i', outSignature: 'i'},
371 {name: 'PurpleConvImSend', inSignature: 'is', outSignature: ''},
372 {name: 'PurpleConvChatSend', inSignature: 'is', outSignature: ''},
373 {name: 'PurpleConvIm', inSignature: 'i', outSignature: 'i'},
374 {name: 'PurpleConvChat', inSignature: 'i', outSignature: 'i'},
375 {name: 'PurpleConvImGetIcon', inSignature: 'i', outSignature: 'i'},
376 {name: 'PurpleConversationGetName', inSignature: 'i', outSignature: 's'},
377 {name: 'PurpleConversationGetAccount', inSignature: 'i', outSignature: 's'},
378 {name: 'PurpleConversationGetMessageHistory', inSignature: 'i', outSignature: 'ai'},
379 {name: 'PurpleConversationMessageGetMessage', inSignature: 'i', outSignature: 's'},
380 {name: 'PurpleConversationGetTitle', inSignature: 'i', outSignature: 's'},
381 {name: 'PurpleConversationHasFocus', inSignature: 'i', outSignature: 'b'}
384 {name: 'ReceivedImMsg', inSignature: 'issiu'},
385 {name: 'ReceivedChatMsg', inSignature: 'issiu'},
386 {name: 'DisplayedImMsg', inSignature: 'issiu'},
387 {name: 'DisplayedChatMsg', inSignature: 'issiu'},
388 {name: 'SentImMsg', inSignature: 'iss'},
389 {name: 'SentChatMsg', inSignature: 'iss'},
390 {name: 'BuddyStatusChanged', inSignature: 'iii'}, // ????
391 {name: 'BuddySignedOff', inSignature: 'i'},
392 {name: 'BuddySignedOn', inSignature: 'i'},
393 {name: 'DeletingConversation', inSignature: 'i'},
394 {name: 'ConversationCreated', inSignature: 'i'}
398 let Pidgin = DBus.makeProxyClass(PidginIface);
400 function PidginClient() {
404 PidginClient.prototype = {
407 this._proxy = new Pidgin(DBus.session, 'im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject');
408 this._displayedImMsgId = 0;
409 this._displayedChatMsgId = 0;
413 this._displayedImMsgId = this._proxy.connect('DisplayedImMsg', Lang.bind(this, this._messageDisplayed));
414 this._displayedChatMsgId = this._proxy.connect('DisplayedChatMsg', Lang.bind(this, this._chatMessageDisplayed));
417 disable: function() {
418 if (this._displayedImMsgId > 0) {
419 this._proxy.disconnect(this._displayedImMsgId);
420 this._displayedImMsgId = 0;
423 if (this._displayedChatMsgId > 0) {
424 this._proxy.disconnect(this._displayedChatMsgId);
425 this._displayedChatMsgId = 0;
428 for (let key in this._sources) {
429 if (this._sources.hasOwnProperty(key))
430 this._sources[key].destroy();
438 _chatMessageDisplayed: function(emitter, account, author, message, conversation, flag) {
440 // only trigger on chat message received with nick
441 if (flag != (2 | 32)) return;
444 let source = this._sources[conversation];
446 source = new Source(this, account, author, message, conversation, true, 2);
447 source.connect('destroy', Lang.bind(this,
449 delete this._sources[conversation];
453 this._sources[conversation] = source;
457 _messageDisplayed: function(emitter, account, author, message, conversation, flag) {
459 // only trigger on message received/message sent
460 if (flag != 2 && flag != 1) return;
463 let source = this._sources[conversation];
465 source = new Source(this, account, author, message, conversation, false, flag);
466 source.connect('destroy', Lang.bind(this,
468 delete this._sources[conversation];
472 this._sources[conversation] = source;
477 function init(metadata) {
478 imports.gettext.bindtextdomain('gnome-shell-extensions', metadata.localedir);
479 return new PidginClient();