[gnome-shell] telepathyClient: Add IM subscription request support
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] telepathyClient: Add IM subscription request support
- Date: Mon, 29 Aug 2011 14:35:52 +0000 (UTC)
commit fefee3b49e1d90bb6a5ae8d8349cfa17336e324b
Author: Xavier Claessens <xclaesse gmail com>
Date: Fri Aug 26 12:27:40 2011 +0200
telepathyClient: Add IM subscription request support
Based on initial work from Guillaume Desmottes
https://bugzilla.gnome.org/show_bug.cgi?id=653941
data/theme/gnome-shell.css | 4 +
js/ui/notificationDaemon.js | 1 +
js/ui/telepathyClient.js | 231 ++++++++++++++++++++++++++++++++++++++++++-
src/shell-tp-client.c | 58 +++++++++++
src/shell-tp-client.h | 14 +++
5 files changed, 306 insertions(+), 2 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index f6f725b..4fe68a8 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1257,6 +1257,10 @@ StTooltip StLabel {
padding-right: 4px;
}
+.subscription-message {
+ font-style: italic;
+}
+
#notification StEntry {
padding: 4px;
border-radius: 4px;
diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js
index 915808f..361c585 100644
--- a/js/ui/notificationDaemon.js
+++ b/js/ui/notificationDaemon.js
@@ -196,6 +196,7 @@ NotificationDaemon.prototype = {
hints['category'] == 'x-empathy.im.room-invitation' ||
hints['category'] == 'x-empathy.call.incoming' ||
hints['category'] == 'x-empathy.call.incoming"' ||
+ hints['category'] == 'x-empathy.im.subscription-request' ||
hints['category'] == 'presence.online' ||
hints['category'] == 'presence.offline')) {
// Ignore replacesId since we already sent back a
diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js
index b90d070..04a4d81 100644
--- a/js/ui/telepathyClient.js
+++ b/js/ui/telepathyClient.js
@@ -87,8 +87,8 @@ Client.prototype = {
// channel matching its filters is detected.
// The second argument, recover, means _observeChannels will be run
// for any existing channel as well.
- let dbus = Tp.DBusDaemon.dup();
- this._tpClient = new Shell.TpClient({ 'dbus_daemon': dbus,
+ this._accountManager = Tp.AccountManager.dup();
+ this._tpClient = new Shell.TpClient({ 'account-manager': this._accountManager,
'name': 'GnomeShell',
'uniquify-name': true })
this._tpClient.set_observe_channels_func(
@@ -98,6 +98,11 @@ Client.prototype = {
this._tpClient.set_handle_channels_func(
Lang.bind(this, this._handleChannels));
+ // Workaround for gjs not supporting GPtrArray in signals.
+ // See BGO bug #653941 for context.
+ this._tpClient.set_contact_list_changed_func(
+ Lang.bind(this, this._contactListChanged));
+
// Allow other clients (such as Empathy) to pre-empt our channels if
// needed
this._tpClient.set_delegated_channels_callback(
@@ -108,6 +113,21 @@ Client.prototype = {
} catch (e) {
throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
}
+
+
+ // Watch subscription requests and connection errors
+ this._subscriptionSource = null;
+ let factory = this._accountManager.get_factory();
+ factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
+ factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
+ factory.add_contact_features([Tp.ContactFeature.SUBSCRIPTION_STATES,
+ Tp.ContactFeature.ALIAS,
+ Tp.ContactFeature.AVATAR_DATA]);
+
+ this._accountManager.connect('account-validity-changed',
+ Lang.bind(this, this._accountValidityChanged));
+
+ this._accountManager.prepare_async(null, Lang.bind(this, this._accountManagerPrepared));
},
_observeChannels: function(observer, account, conn, channels,
@@ -337,6 +357,79 @@ Client.prototype = {
_delegatedChannelsCb: function(client, channels) {
// Nothing to do as we don't make a distinction between observed and
// handled channels.
+ },
+
+ _accountManagerPrepared: function(am, result) {
+ am.prepare_finish(result);
+
+ let accounts = am.get_valid_accounts();
+ for (let i = 0; i < accounts.length; i++) {
+ this._accountValidityChanged(am, accounts[i], true);
+ }
+ },
+
+ _accountValidityChanged: function(am, account, valid) {
+ if (!valid)
+ return;
+
+ account.connect('notify::connection',
+ Lang.bind(this, this._connectionChanged));
+ this._connectionChanged(account);
+ },
+
+ _connectionChanged: function(account) {
+ let conn = account.get_connection();
+ if (conn == null)
+ return;
+
+ this._tpClient.grab_contact_list_changed(conn);
+ if (conn.get_contact_list_state() == Tp.ContactListState.SUCCESS) {
+ this._contactListChanged(conn, conn.dup_contact_list(), []);
+ }
+ },
+
+ _contactListChanged: function(conn, added, removed) {
+ for (let i = 0; i < added.length; i++) {
+ let contact = added[i];
+
+ contact.connect('subscription-states-changed',
+ Lang.bind(this, this._subscriptionStateChanged));
+ this._subscriptionStateChanged(contact);
+ }
+ },
+
+ _subscriptionStateChanged: function(contact) {
+ if (contact.get_publish_state() != Tp.SubscriptionState.ASK)
+ return;
+
+ /* Implicitly accept publish requests if contact is already subscribed */
+ if (contact.get_subscribe_state() == Tp.SubscriptionState.YES ||
+ contact.get_subscribe_state() == Tp.SubscriptionState.ASK) {
+
+ contact.authorize_publication_async(function(src, result) {
+ src.authorize_publication_finish(result)});
+
+ return;
+ }
+
+ /* Display notification to ask user to accept/reject request */
+ let source = this._ensureSubscriptionSource();
+ Main.messageTray.add(source);
+
+ let notif = new SubscriptionRequestNotification(source, contact);
+ source.notify(notif);
+ },
+
+ _ensureSubscriptionSource: function() {
+ if (this._subscriptionSource == null) {
+ this._subscriptionSource = new MultiNotificationSource(
+ _("Subscription request"), 'gtk-dialog-question');
+ this._subscriptionSource.connect('destroy', Lang.bind(this, function () {
+ this._subscriptionSource = null;
+ }));
+ }
+
+ return this._subscriptionSource;
}
};
@@ -1118,3 +1211,137 @@ FileTransferNotification.prototype = {
}));
}
};
+
+// A notification source that can embed multiple notifications
+function MultiNotificationSource(title, icon) {
+ this._init(title, icon);
+}
+
+MultiNotificationSource.prototype = {
+ __proto__: MessageTray.Source.prototype,
+
+ _init: function(title, icon) {
+ MessageTray.Source.prototype._init.call(this, title);
+
+ this._icon = icon;
+ this._setSummaryIcon(this.createNotificationIcon());
+ this._nbNotifications = 0;
+ },
+
+ notify: function(notification) {
+ MessageTray.Source.prototype.notify.call(this, notification);
+
+ this._nbNotifications += 1;
+
+ // Display the source while there is at least one notification
+ notification.connect('destroy', Lang.bind(this, function () {
+ this._nbNotifications -= 1;
+
+ if (this._nbNotifications == 0)
+ this.destroy();
+ }));
+ },
+
+ createNotificationIcon: function() {
+ return new St.Icon({ gicon: Shell.util_icon_from_string(this._icon),
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: this.ICON_SIZE });
+ }
+};
+
+// Subscription request
+function SubscriptionRequestNotification(source, contact) {
+ this._init(source, contact);
+}
+
+SubscriptionRequestNotification.prototype = {
+ __proto__: MessageTray.Notification.prototype,
+
+ _init: function(source, contact) {
+ MessageTray.Notification.prototype._init.call(this, source,
+ /* To translators: The parameter is the contact's alias */
+ _("%s would like permission to see when you are online").format(contact.get_alias()),
+ null, { customContent: true });
+
+ this._contact = contact;
+ this._connection = contact.get_connection();
+
+ let layout = new St.BoxLayout({ vertical: false });
+
+ // Display avatar
+ let iconBox = new St.Bin({ style_class: 'avatar-box' });
+ iconBox._size = 48;
+
+ let textureCache = St.TextureCache.get_default();
+ let file = contact.get_avatar_file();
+
+ if (file) {
+ let uri = file.get_uri();
+ iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size);
+ }
+ else {
+ iconBox.child = new St.Icon({ icon_name: 'avatar-default',
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: iconBox._size });
+ }
+
+ layout.add(iconBox);
+
+ // subscription request message
+ let label = new St.Label({ style_class: 'subscription-message',
+ text: contact.get_publish_request() });
+
+ layout.add(label);
+
+ this.addActor(layout);
+
+ this.addButton('decline', _("Decline"));
+ this.addButton('accept', _("Accept"));
+
+ this.connect('action-invoked', Lang.bind(this, function(self, action) {
+ switch (action) {
+ case 'decline':
+ contact.remove_async(function(src, result) {
+ src.remove_finish(result)});
+ break;
+ case 'accept':
+ // Authorize the contact and request to see his status as well
+ contact.authorize_publication_async(function(src, result) {
+ src.authorize_publication_finish(result)});
+
+ contact.request_subscription_async('', function(src, result) {
+ src.request_subscription_finish(result)});
+ break;
+ }
+
+ // rely on _subscriptionStatesChangedCb to destroy the
+ // notification
+ }));
+
+ this._changedId = contact.connect('subscription-states-changed',
+ Lang.bind(this, this._subscriptionStatesChangedCb));
+ this._invalidatedId = this._connection.connect('invalidated',
+ Lang.bind(this, this.destroy));
+ },
+
+ destroy: function() {
+ if (this._changedId != 0) {
+ this._contact.disconnect(this._changedId);
+ this._changedId = 0;
+ }
+
+ if (this._invalidatedId != 0) {
+ this._connection.disconnect(this._invalidatedId);
+ this._invalidatedId = 0;
+ }
+
+ MessageTray.Notification.prototype.destroy.call(this);
+ },
+
+ _subscriptionStatesChangedCb: function(contact, subscribe, publish, msg) {
+ // Destroy the notification if the subscription request has been
+ // answered
+ if (publish != Tp.SubscriptionState.ASK)
+ this.destroy();
+ }
+};
diff --git a/src/shell-tp-client.c b/src/shell-tp-client.c
index 48233e2..ee647f0 100644
--- a/src/shell-tp-client.c
+++ b/src/shell-tp-client.c
@@ -19,6 +19,10 @@ struct _ShellTpClientPrivate
ShellTpClientHandleChannelsImpl handle_channels_impl;
gpointer user_data_handle_channels;
GDestroyNotify destroy_handle_channels;
+
+ ShellTpClientContactListChangedImpl contact_list_changed_impl;
+ gpointer user_data_contact_list_changed;
+ GDestroyNotify destroy_contact_list_changed;
};
/**
@@ -77,6 +81,16 @@ struct _ShellTpClientPrivate
* Signature of the implementation of the HandleChannels method.
*/
+/**
+ * ShellTpClientContactListChangedImpl:
+ * @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared
+ * if possible
+ * @added: (element-type TelepathyGLib.Contact): a #GPtrArray of added #TpContact
+ * @removed: (element-type TelepathyGLib.Contact): a #GPtrArray of removed #TpContact
+ *
+ * Signature of the implementation of the ContactListChanged method.
+ */
+
static void
shell_tp_client_init (ShellTpClient *self)
{
@@ -220,6 +234,13 @@ shell_tp_client_dispose (GObject *object)
self->priv->user_data_handle_channels = NULL;
}
+ if (self->priv->destroy_contact_list_changed != NULL)
+ {
+ self->priv->destroy_contact_list_changed (self->priv->user_data_contact_list_changed);
+ self->priv->destroy_contact_list_changed = NULL;
+ self->priv->user_data_contact_list_changed = NULL;
+ }
+
if (dispose != NULL)
dispose (object);
}
@@ -278,6 +299,43 @@ shell_tp_client_set_handle_channels_func (ShellTpClient *self,
self->priv->destroy_handle_channels = destroy;
}
+void
+shell_tp_client_set_contact_list_changed_func (ShellTpClient *self,
+ ShellTpClientContactListChangedImpl contact_list_changed_impl,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ g_assert (self->priv->contact_list_changed_impl == NULL);
+
+ self->priv->contact_list_changed_impl = contact_list_changed_impl;
+ self->priv->user_data_handle_channels = user_data;
+ self->priv->destroy_handle_channels = destroy;
+}
+
+static void
+on_contact_list_changed (TpConnection *conn,
+ GPtrArray *added,
+ GPtrArray *removed,
+ gpointer user_data)
+{
+ ShellTpClient *self = (ShellTpClient *) user_data;
+
+ g_assert (self->priv->contact_list_changed_impl != NULL);
+
+ self->priv->contact_list_changed_impl (conn,
+ added, removed,
+ self->priv->user_data_contact_list_changed);
+}
+
+void
+shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
+ TpConnection *conn)
+{
+ g_signal_connect (conn, "contact-list-changed",
+ G_CALLBACK (on_contact_list_changed),
+ self);
+}
+
/* Telepathy utility functions */
/**
diff --git a/src/shell-tp-client.h b/src/shell-tp-client.h
index b323045..6e01388 100644
--- a/src/shell-tp-client.h
+++ b/src/shell-tp-client.h
@@ -85,6 +85,20 @@ void shell_tp_client_set_handle_channels_func (ShellTpClient *self,
gpointer user_data,
GDestroyNotify destroy);
+typedef void (*ShellTpClientContactListChangedImpl) (
+ TpConnection *connection,
+ GPtrArray *added,
+ GPtrArray *removed,
+ gpointer user_data);
+
+void shell_tp_client_set_contact_list_changed_func (ShellTpClient *self,
+ ShellTpClientContactListChangedImpl contact_list_changed_impl,
+ gpointer user_data,
+ GDestroyNotify destroy);
+
+void shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
+ TpConnection *conn);
+
/* Telepathy utility functions */
typedef void (*ShellGetTpContactCb) (TpConnection *connection,
GList *contacts,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]