6d292945b6
TelepathyClient.Notification was renamed to ChatNotification, a setChatState method was added in the Source API too and the timestamp management changed a bit. https://bugzilla.gnome.org/show_bug.cgi?id=657475
341 lines
11 KiB
JavaScript
341 lines
11 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const DBus = imports.dbus;
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
const Gio = imports.gi.Gio;
|
|
const GLib = imports.gi.GLib;
|
|
const Lang = imports.lang;
|
|
const Signals = imports.signals;
|
|
const St = imports.gi.St;
|
|
const Tp = imports.gi.TelepathyGLib;
|
|
|
|
const Main = imports.ui.main;
|
|
const Mainloop = imports.mainloop;
|
|
const MessageTray = imports.ui.messageTray;
|
|
const Shell = imports.gi.Shell;
|
|
const TelepathyClient = imports.ui.telepathyClient;
|
|
|
|
const _ = Gettext.gettext;
|
|
|
|
// http://ntt.cc/ext/base64-Encoding-Decoding.html
|
|
const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
|
|
function decode64(input) {
|
|
let output = "";
|
|
let chr1, chr2, chr3;
|
|
let enc1, enc2, enc3, enc4;
|
|
let i = 0;
|
|
|
|
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
|
|
|
do {
|
|
enc1 = keyStr.indexOf(input.charAt(i++));
|
|
enc2 = keyStr.indexOf(input.charAt(i++));
|
|
enc3 = keyStr.indexOf(input.charAt(i++));
|
|
enc4 = keyStr.indexOf(input.charAt(i++));
|
|
|
|
chr1 = (enc1 << 2) | (enc2 >> 4);
|
|
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
|
chr3 = ((enc3 & 3) << 6) | enc4;
|
|
|
|
output = output + String.fromCharCode(chr1);
|
|
|
|
if (enc3 != 64) {
|
|
output = output + String.fromCharCode(chr2);
|
|
}
|
|
if (enc4 != 64) {
|
|
output = output + String.fromCharCode(chr3);
|
|
}
|
|
|
|
chr1 = chr2 = chr3 = "";
|
|
enc1 = enc2 = enc3 = enc4 = "";
|
|
|
|
} while (i < input.length);
|
|
|
|
return unescape(output);
|
|
}
|
|
|
|
function wrappedText(text, sender, timestamp, direction) {
|
|
if (!timestamp)
|
|
timestamp = (Date.now() / 1000);
|
|
return {
|
|
messageType: Tp.ChannelTextMessageType.NORMAL,
|
|
text: text,
|
|
sender: sender,
|
|
timestamp: timestamp,
|
|
direction: direction
|
|
};
|
|
}
|
|
|
|
function Source(gajimClient, accountName, author, initialMessage) {
|
|
this._init(gajimClient, accountName, author, initialMessage);
|
|
}
|
|
|
|
Source.prototype = {
|
|
__proto__: MessageTray.Source.prototype,
|
|
|
|
_init: function(gajimClient, accountName, author, initialMessage) {
|
|
MessageTray.Source.prototype._init.call(this, author);
|
|
this.isChat = true;
|
|
this._author = author;
|
|
this._gajimClient = gajimClient;
|
|
this._accountName = accountName;
|
|
this._initialMessage = initialMessage;
|
|
this._iconUri = null;
|
|
this._presence = "online";
|
|
|
|
this._notification = new TelepathyClient.ChatNotification(this);
|
|
this._notification.setUrgency(MessageTray.Urgency.HIGH);
|
|
|
|
let jid = author.split('/')[0];
|
|
let proxy = this._gajimClient.proxy();
|
|
proxy.contact_infoRemote(jid, Lang.bind(this, this._gotContactInfos));
|
|
this._statusChangeId = proxy.connect('ContactStatus',
|
|
Lang.bind(this, this._onStatusChange));
|
|
this._contactAbsenceId = proxy.connect('ContactAbsence',
|
|
Lang.bind(this, this._onStatusChange));
|
|
this._chatStateId = proxy.connect('ChatState',
|
|
Lang.bind(this, this._onChatState));
|
|
this._messageSentId = proxy.connect('MessageSent',
|
|
Lang.bind(this, this._messageSent));
|
|
this._newMessageId = proxy.connect('NewMessage',
|
|
Lang.bind(this, this._messageReceived));
|
|
},
|
|
|
|
destroy: function() {
|
|
let proxy = this._gajimClient.proxy();
|
|
proxy.disconnect(this._statusChangeId);
|
|
proxy.disconnect(this._contactAbsenceId);
|
|
proxy.disconnect(this._chatStateId);
|
|
proxy.disconnect(this._messageSentId);
|
|
proxy.disconnect(this._newMessageId);
|
|
MessageTray.Source.prototype.destroy.call(this);
|
|
},
|
|
|
|
_gotContactInfos: function(result, excp) {
|
|
this.title = result['FN'];
|
|
|
|
let avatarUri = null;
|
|
if (result['PHOTO']) {
|
|
let mimeType = result['PHOTO']['TYPE'];
|
|
let avatarData = decode64(result['PHOTO']['BINVAL']);
|
|
let sha = result['PHOTO']['SHA'];
|
|
avatarUri = this._gajimClient.cacheAvatar(mimeType, sha, avatarData);
|
|
}
|
|
|
|
this._iconUri = avatarUri;
|
|
this._setSummaryIcon(this.createNotificationIcon());
|
|
|
|
let message = wrappedText(this._initialMessage, this._author, null, TelepathyClient.NotificationDirection.RECEIVED);
|
|
this._notification.appendMessage(message, false);
|
|
|
|
if (!Main.messageTray.contains(this))
|
|
Main.messageTray.add(this);
|
|
|
|
this.notify(this._notification);
|
|
},
|
|
|
|
createNotificationIcon: function() {
|
|
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
|
iconBox._size = this.ICON_SIZE;
|
|
|
|
if (!this._iconUri) {
|
|
iconBox.child = new St.Icon({ icon_name: 'avatar-default',
|
|
icon_type: St.IconType.FULLCOLOR,
|
|
icon_size: iconBox._size });
|
|
} else {
|
|
let textureCache = St.TextureCache.get_default();
|
|
iconBox.child = textureCache.load_uri_async(this._iconUri, iconBox._size, iconBox._size);
|
|
}
|
|
return iconBox;
|
|
},
|
|
|
|
open: function(notification) {
|
|
// Lookup for the messages window and display it. In the case where it's not o
|
|
// opened yet fallback to the roster window.
|
|
let windows = global.get_window_actors();
|
|
for (let i = 0; i < windows.length; i++) {
|
|
let metaWindow = windows[i].metaWindow;
|
|
if (metaWindow.get_wm_class_instance() == "gajim" &&
|
|
metaWindow.get_role() == "messages") {
|
|
Main.activateWindow(metaWindow);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let app = Shell.AppSystem.get_default().lookup_app('gajim.desktop');
|
|
app.activate(-1);
|
|
},
|
|
|
|
_onChatState: function(emitter, data) {
|
|
let chatstate = data[1][5];
|
|
if (chatstate == 'gone')
|
|
this.destroy();
|
|
},
|
|
|
|
setChatState: function(state) {
|
|
// Gajim DBUS API doesn't support sending chatstate yet.
|
|
},
|
|
|
|
_messageReceived: function(emitter, data) {
|
|
let author = data[1][0];
|
|
let text = data[1][1];
|
|
if (text && (author == this._author)) {
|
|
let message = wrappedText(text, this._author, null, TelepathyClient.NotificationDirection.RECEIVED);
|
|
this._notification.appendMessage(message, false);
|
|
this.notify(this._notification);
|
|
}
|
|
},
|
|
|
|
_messageSent: function(emitter, data) {
|
|
let recipient = data[1][0];
|
|
let text = data[1][1];
|
|
let chatstate = data[1][3];
|
|
|
|
if (text && (recipient == this._author)) {
|
|
let message = wrappedText(text, this._author, null, TelepathyClient.NotificationDirection.SENT);
|
|
this._notification.appendMessage(message, false);
|
|
} else if (chatstate == 'gone')
|
|
this.destroy();
|
|
},
|
|
|
|
notify: function() {
|
|
|
|
MessageTray.Source.prototype.notify.call(this, this._notification);
|
|
},
|
|
|
|
respond: function(text) {
|
|
let jid = this._author;
|
|
let keyID = ""; // unencrypted.
|
|
this._gajimClient.proxy().send_chat_messageRemote(jid, text, keyID, this._accountName);
|
|
},
|
|
|
|
_onStatusChange: function(emitter, data) {
|
|
if (!this.title)
|
|
return;
|
|
|
|
let jid = data[1][0];
|
|
let presence = data[1][1];
|
|
let message = data[1][2];
|
|
|
|
if (jid != this._author.split('/')[0])
|
|
return;
|
|
|
|
let presenceMessage, shouldNotify, title;
|
|
title = GLib.markup_escape_text(this.title, -1);
|
|
if (presence == "away") {
|
|
presenceMessage = _("%s is away.").format(title);
|
|
shouldNotify = false;
|
|
} else if (presence == "offline") {
|
|
presenceMessage = _("%s is offline.").format(title);
|
|
shouldNotify = (this._presence != "offline");
|
|
} else if (presence == "online") {
|
|
presenceMessage = _("%s is online.").format(title);
|
|
shouldNotify = (this._presence == "offline");
|
|
} else if (presence == "dnd") {
|
|
presenceMessage = _("%s is busy.").format(title);
|
|
shouldNotify = false;
|
|
} else
|
|
return;
|
|
|
|
this._presence = presence;
|
|
|
|
if (message)
|
|
presenceMessage += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>';
|
|
|
|
this._notification.appendPresence(presenceMessage, shouldNotify);
|
|
if (shouldNotify)
|
|
this.notify(this._notification);
|
|
}
|
|
};
|
|
|
|
|
|
const GajimIface = {
|
|
name: 'org.gajim.dbus.RemoteInterface',
|
|
properties: [],
|
|
methods: [{ name: 'send_chat_message', inSignature: 'ssss', outSignature: 'b'},
|
|
{ name: 'contact_info', inSignature: 's', outSignature: 'a{sv}'}],
|
|
signals: [{ name: 'NewMessage', inSignature: 'av' },
|
|
{ name: 'ChatState', inSignature: 'av' },
|
|
{ name: 'ContactStatus', inSignature: 'av' },
|
|
{ name: 'ContactAbsence', inSignature: 'av' },
|
|
{ name: 'MessageSent', inSignature: 'av' }]
|
|
};
|
|
|
|
let Gajim = DBus.makeProxyClass(GajimIface);
|
|
|
|
function GajimClient() {
|
|
this._init();
|
|
}
|
|
|
|
GajimClient.prototype = {
|
|
_init: function() {
|
|
this._sources = {};
|
|
},
|
|
|
|
proxy : function() {
|
|
return this._proxy;
|
|
},
|
|
|
|
enable: function() {
|
|
this._cacheDir = GLib.get_user_cache_dir() + '/gnome-shell/gajim-avatars';
|
|
let dir = Gio.file_new_for_path(this._cacheDir);
|
|
if (!dir.query_exists(null)) {
|
|
GLib.mkdir_with_parents(this._cacheDir, 0x1c0); // 0x1c0 = octal 0700
|
|
}
|
|
|
|
this._proxy = new Gajim(DBus.session, 'org.gajim.dbus', '/org/gajim/dbus/RemoteObject');
|
|
this._newMessageId = this._proxy.connect('NewMessage', Lang.bind(this, this._messageReceived));
|
|
},
|
|
|
|
disable: function() {
|
|
if (this._newMessageId) {
|
|
this._proxy.disconnect(this._newMessageId);
|
|
this._newMessageId = 0;
|
|
}
|
|
this._proxy = null;
|
|
|
|
for (let id in this._sources)
|
|
this._sources[id].destroy();
|
|
|
|
this._sources = { };
|
|
},
|
|
|
|
_messageReceived : function(emitter, data) {
|
|
let author = data[1][0];
|
|
let message = data[1][1];
|
|
let account = data[0];
|
|
let source = this._sources[author];
|
|
if (!source) {
|
|
source = new Source(this, account, author, message);
|
|
source.connect('destroy', Lang.bind(this,
|
|
function() {
|
|
delete this._sources[author];
|
|
}));
|
|
this._sources[author] = source;
|
|
}
|
|
},
|
|
|
|
cacheAvatar : function(mimeType, sha, avatarData) {
|
|
let ext = mimeType.split('/')[1];
|
|
let file = this._cacheDir + '/' + sha + '.' + ext;
|
|
let uri = GLib.filename_to_uri(file, null);
|
|
|
|
if (GLib.file_test(file, GLib.FileTest.EXISTS))
|
|
return uri;
|
|
|
|
let success = false;
|
|
try {
|
|
success = GLib.file_set_contents(file, avatarData, avatarData.length);
|
|
} catch (e) {
|
|
logError(e, 'Error caching avatar data');
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
};
|
|
|
|
function init() {
|
|
return new GajimClient();
|
|
}
|