[gnome-shell] telepathyClient: Add IM subscription request support



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]