]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-tp-chat.c
Merge branch 'sasl'
[empathy.git] / libempathy / empathy-tp-chat.c
index 54e08d07139cebb04b921338a88dd5ab936349e5..f1351049aedf8809d421fce0834a63eeb8514ba9 100644 (file)
@@ -15,7 +15,7 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- * 
+ *
  * Authors: Xavier Claessens <xclaesse@gmail.com>
  */
 
 
 #include <string.h>
 
-#include <telepathy-glib/channel.h>
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/util.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <extensions/extensions.h>
 
 #include "empathy-tp-chat.h"
 #include "empathy-tp-contact-factory.h"
-#include "empathy-contact-monitor.h"
 #include "empathy-contact-list.h"
 #include "empathy-marshal.h"
 #include "empathy-time.h"
@@ -41,8 +40,8 @@
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpChat)
 typedef struct {
        gboolean               dispose_has_run;
-       EmpathyTpContactFactory *factory;
-       EmpathyContactMonitor *contact_monitor;
+       TpAccount             *account;
+       TpConnection          *connection;
        EmpathyContact        *user;
        EmpathyContact        *remote_contact;
        GList                 *members;
@@ -54,22 +53,22 @@ typedef struct {
        GQueue                *pending_messages_queue;
        gboolean               had_properties_list;
        GPtrArray             *properties;
+       TpChannelPasswordFlags password_flags;
+       /* TRUE if we fetched the password flag of the channel or if it's not needed
+        * (channel doesn't implement the Password interface) */
+       gboolean               got_password_flags;
        gboolean               ready;
+       gboolean               can_upgrade_to_muc;
 } EmpathyTpChatPriv;
 
-typedef struct {
-       gchar          *name;
-       guint           id;
-       TpPropertyFlags flags;
-       GValue         *value;
-} TpChatProperty;
-
 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
 
 enum {
        PROP_0,
+       PROP_ACCOUNT,
        PROP_CHANNEL,
        PROP_REMOTE_CONTACT,
+       PROP_PASSWORD_NEEDED,
        PROP_READY,
 };
 
@@ -108,7 +107,21 @@ tp_chat_async_cb (TpChannel *proxy,
                  GObject *weak_object)
 {
        if (error) {
-               DEBUG ("Error %s: %s", (gchar*) user_data, error->message);
+               DEBUG ("Error %s: %s", (gchar *) user_data, error->message);
+       }
+}
+
+static void
+create_conference_cb (GObject *source,
+                     GAsyncResult *result,
+                     gpointer user_data)
+{
+       GError *error = NULL;
+
+       if (!tp_account_channel_request_create_channel_finish (
+                       TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error)) {
+               DEBUG ("Failed to create conference channel: %s", error->message);
+               g_error_free (error);
        }
 }
 
@@ -118,17 +131,53 @@ tp_chat_add (EmpathyContactList *list,
             const gchar        *message)
 {
        EmpathyTpChatPriv *priv = GET_PRIV (list);
-       TpHandle           handle;
-       GArray             handles = {(gchar *) &handle, 1};
 
-       g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
-       g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+       if (tp_proxy_has_interface_by_id (priv->channel,
+               TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
+               TpHandle           handle;
+               GArray             handles = {(gchar *) &handle, 1};
 
-       handle = empathy_contact_get_handle (contact);
-       tp_cli_channel_interface_group_call_add_members (priv->channel, -1,
-                                                        &handles, NULL,
-                                                        NULL, NULL, NULL,
-                                                        NULL);
+               g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
+               g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+               handle = empathy_contact_get_handle (contact);
+               tp_cli_channel_interface_group_call_add_members (priv->channel,
+                       -1, &handles, NULL, NULL, NULL, NULL, NULL);
+       } else if (priv->can_upgrade_to_muc) {
+               TpAccountChannelRequest *req;
+               GHashTable        *props;
+               const char        *object_path;
+               GPtrArray          channels = { (gpointer *) &object_path, 1 };
+               const char        *invitees[2] = { NULL, };
+
+               invitees[0] = empathy_contact_get_id (contact);
+               object_path = tp_proxy_get_object_path (priv->channel);
+
+               props = tp_asv_new (
+                   TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+                       TP_IFACE_CHANNEL_TYPE_TEXT,
+                   TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+                       TP_HANDLE_TYPE_NONE,
+                   TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS,
+                       TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels,
+                   TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_IDS,
+                       G_TYPE_STRV, invitees,
+                   /* FIXME: InvitationMessage ? */
+                   NULL);
+
+               req = tp_account_channel_request_new (priv->account, props,
+                       TP_USER_ACTION_TIME_NOT_USER_ACTION);
+
+               /* Although this is a MUC, it's anonymous, so CreateChannel is
+                * valid. */
+               tp_account_channel_request_create_channel_async (req, NULL, NULL,
+                       create_conference_cb, NULL);
+
+               g_object_unref (req);
+               g_hash_table_unref (props);
+       } else {
+               g_warning ("Cannot add to this channel");
+       }
 }
 
 static void
@@ -163,26 +212,28 @@ tp_chat_get_members (EmpathyContactList *list)
                g_list_foreach (members, (GFunc) g_object_ref, NULL);
        } else {
                members = g_list_prepend (members, g_object_ref (priv->user));
-               members = g_list_prepend (members, g_object_ref (priv->remote_contact));
+               if (priv->remote_contact != NULL)
+                       members = g_list_prepend (members, g_object_ref (priv->remote_contact));
        }
 
        return members;
 }
 
-static EmpathyContactMonitor *
-tp_chat_get_monitor (EmpathyContactList *list)
+static void
+check_ready (EmpathyTpChat *chat)
 {
-       EmpathyTpChatPriv *priv;
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
 
-       g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
+       if (priv->ready)
+               return;
 
-       priv = GET_PRIV (list);
+       if (g_queue_get_length (priv->messages_queue) > 0)
+               return;
 
-       if (priv->contact_monitor == NULL) {
-               priv->contact_monitor = empathy_contact_monitor_new_for_iface (list);
-       }
+       DEBUG ("Ready");
 
-       return priv->contact_monitor;
+       priv->ready = TRUE;
+       g_object_notify (G_OBJECT (chat), "ready");
 }
 
 static void
@@ -202,10 +253,12 @@ tp_chat_emit_queued_messages (EmpathyTpChat *chat)
                g_queue_push_tail (priv->pending_messages_queue, message);
                g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
        }
+
+       check_ready (chat);
 }
 
 static void
-tp_chat_got_sender_cb (EmpathyTpContactFactory *factory,
+tp_chat_got_sender_cb (TpConnection            *connection,
                       EmpathyContact          *contact,
                       const GError            *error,
                       gpointer                 message,
@@ -226,11 +279,13 @@ tp_chat_got_sender_cb (EmpathyTpContactFactory *factory,
 
 static void
 tp_chat_build_message (EmpathyTpChat *chat,
+                      gboolean       incoming,
                       guint          id,
                       guint          type,
                       guint          timestamp,
                       guint          from_handle,
-                      const gchar   *message_body)
+                      const gchar   *message_body,
+                      TpChannelTextMessageFlags flags)
 {
        EmpathyTpChatPriv *priv;
        EmpathyMessage    *message;
@@ -242,13 +297,19 @@ tp_chat_build_message (EmpathyTpChat *chat,
        empathy_message_set_receiver (message, priv->user);
        empathy_message_set_timestamp (message, timestamp);
        empathy_message_set_id (message, id);
+       empathy_message_set_incoming (message, incoming);
+       empathy_message_set_flags (message, flags);
+
+       if (flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK)
+               empathy_message_set_is_backlog (message, TRUE);
+
        g_queue_push_tail (priv->messages_queue, message);
 
        if (from_handle == 0) {
                empathy_message_set_sender (message, priv->user);
                tp_chat_emit_queued_messages (chat);
        } else {
-               empathy_tp_contact_factory_get_from_handle (priv->factory,
+               empathy_tp_contact_factory_get_from_handle (priv->connection,
                        from_handle,
                        tp_chat_got_sender_cb,
                        message, NULL, G_OBJECT (chat));
@@ -275,7 +336,7 @@ tp_chat_received_cb (TpChannel   *channel,
        if (priv->listing_pending_messages) {
                return;
        }
+
        DEBUG ("Message received: %s", message_body);
 
        if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
@@ -293,11 +354,13 @@ tp_chat_received_cb (TpChannel   *channel,
        }
 
        tp_chat_build_message (chat,
+                              TRUE,
                               message_id,
                               message_type,
                               timestamp,
                               from_handle,
-                              message_body);
+                              message_body,
+                              message_flags);
 }
 
 static void
@@ -317,11 +380,13 @@ tp_chat_sent_cb (TpChannel   *channel,
        DEBUG ("Message sent: %s", message_body);
 
        tp_chat_build_message (chat,
+                              FALSE,
                               0,
                               message_type,
                               timestamp,
                               0,
-                              message_body);
+                              message_body,
+                              0);
 }
 
 static void
@@ -338,14 +403,9 @@ tp_chat_send_error_cb (TpChannel   *channel,
        if (priv->channel == NULL)
                return;
 
-       DEBUG ("Message sent error: %s (%d)", message_body, error_code);
+       DEBUG ("Error sending '%s' (%d)", message_body, error_code);
 
-       tp_chat_build_message (EMPATHY_TP_CHAT (chat),
-                              0,
-                              message_type,
-                              timestamp,
-                              0,
-                              message_body);
+       g_signal_emit (chat, signals[SEND_ERROR], 0, message_body, error_code);
 }
 
 static void
@@ -358,7 +418,8 @@ tp_chat_send_cb (TpChannel    *proxy,
 
        if (error) {
                DEBUG ("Error: %s", error->message);
-               g_signal_emit (chat, signals[SEND_ERROR], 0, message,
+               g_signal_emit (chat, signals[SEND_ERROR], 0,
+                              empathy_message_get_body (message),
                               TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN);
        }
 }
@@ -369,7 +430,7 @@ typedef struct {
 } StateChangedData;
 
 static void
-tp_chat_state_changed_got_contact_cb (EmpathyTpContactFactory *factory,
+tp_chat_state_changed_got_contact_cb (TpConnection            *connection,
                                      EmpathyContact          *contact,
                                      const GError            *error,
                                      gpointer                 user_data,
@@ -384,7 +445,7 @@ tp_chat_state_changed_got_contact_cb (EmpathyTpContactFactory *factory,
 
        state = GPOINTER_TO_UINT (user_data);
        DEBUG ("Chat state changed for %s (%d): %d",
-               empathy_contact_get_name (contact),
+               empathy_contact_get_alias (contact),
                empathy_contact_get_handle (contact), state);
 
        g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
@@ -399,7 +460,7 @@ tp_chat_state_changed_cb (TpChannel *channel,
 {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
 
-       empathy_tp_contact_factory_get_from_handle (priv->factory, handle,
+       empathy_tp_contact_factory_get_from_handle (priv->connection, handle,
                tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state),
                NULL, chat);
 }
@@ -459,17 +520,21 @@ tp_chat_list_pending_messages_cb (TpChannel       *channel,
                }
 
                tp_chat_build_message (chat,
+                                      TRUE,
                                       message_id,
                                       message_type,
                                       timestamp,
                                       from_handle,
-                                      message_body);
+                                      message_body,
+                                      message_flags);
        }
 
        if (empty_non_text_content_ids != NULL) {
                acknowledge_messages (chat, empty_non_text_content_ids);
                g_array_free (empty_non_text_content_ids, TRUE);
        }
+
+       check_ready (chat);
 }
 
 static void
@@ -489,10 +554,10 @@ tp_chat_property_flags_changed_cb (TpProxy         *proxy,
        }
 
        for (i = 0; i < properties->len; i++) {
-               GValueArray    *prop_struct;
-               TpChatProperty *property;
-               guint           id;
-               guint           flags;
+               GValueArray           *prop_struct;
+               EmpathyTpChatProperty *property;
+               guint                  id;
+               guint                  flags;
 
                prop_struct = g_ptr_array_index (properties, i);
                id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
@@ -527,10 +592,10 @@ tp_chat_properties_changed_cb (TpProxy         *proxy,
        }
 
        for (i = 0; i < properties->len; i++) {
-               GValueArray    *prop_struct;
-               TpChatProperty *property;
-               guint           id;
-               GValue         *src_value;
+               GValueArray           *prop_struct;
+               EmpathyTpChatProperty *property;
+               guint                  id;
+               GValue                *src_value;
 
                prop_struct = g_ptr_array_index (properties, i);
                id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
@@ -593,11 +658,11 @@ tp_chat_list_properties_cb (TpProxy         *proxy,
        ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
        priv->properties = g_ptr_array_sized_new (properties->len);
        for (i = 0; i < properties->len; i++) {
-               GValueArray    *prop_struct;
-               TpChatProperty *property;
+               GValueArray           *prop_struct;
+               EmpathyTpChatProperty *property;
 
                prop_struct = g_ptr_array_index (properties, i);
-               property = g_slice_new0 (TpChatProperty);
+               property = g_slice_new0 (EmpathyTpChatProperty);
                property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
                property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
                property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
@@ -624,9 +689,13 @@ empathy_tp_chat_set_property (EmpathyTpChat *chat,
                              const gchar   *name,
                              const GValue  *value)
 {
-       EmpathyTpChatPriv *priv = GET_PRIV (chat);
-       TpChatProperty    *property;
-       guint              i;
+       EmpathyTpChatPriv     *priv = GET_PRIV (chat);
+       EmpathyTpChatProperty *property;
+       guint                  i;
+
+       if (!priv->had_properties_list) {
+               return;
+       }
 
        for (i = 0; i < priv->properties->len; i++) {
                property = g_ptr_array_index (priv->properties, i);
@@ -668,6 +737,36 @@ empathy_tp_chat_set_property (EmpathyTpChat *chat,
        }
 }
 
+EmpathyTpChatProperty *
+empathy_tp_chat_get_property (EmpathyTpChat *chat,
+                             const gchar   *name)
+{
+       EmpathyTpChatPriv     *priv = GET_PRIV (chat);
+       EmpathyTpChatProperty *property;
+       guint                  i;
+
+       if (!priv->had_properties_list) {
+               return NULL;
+       }
+
+       for (i = 0; i < priv->properties->len; i++) {
+               property = g_ptr_array_index (priv->properties, i);
+               if (!tp_strdiff (property->name, name)) {
+                       return property;
+               }
+       }
+
+       return NULL;
+}
+
+GPtrArray *
+empathy_tp_chat_get_properties (EmpathyTpChat *chat)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
+
+       return priv->properties;
+}
+
 static void
 tp_chat_dispose (GObject *object)
 {
@@ -679,6 +778,12 @@ tp_chat_dispose (GObject *object)
 
        priv->dispose_has_run = TRUE;
 
+       tp_clear_object (&priv->account);
+
+       if (priv->connection != NULL)
+               g_object_unref (priv->connection);
+       priv->connection = NULL;
+
        if (priv->channel != NULL) {
                g_signal_handlers_disconnect_by_func (priv->channel,
                        tp_chat_invalidated_cb, self);
@@ -690,18 +795,10 @@ tp_chat_dispose (GObject *object)
                g_object_unref (priv->remote_contact);
        priv->remote_contact = NULL;
 
-       if (priv->factory != NULL)
-               g_object_unref (priv->factory);
-       priv->factory = NULL;
-
-       if (priv->user != NULL);
+       if (priv->user != NULL)
                g_object_unref (priv->user);
        priv->user = NULL;
 
-       if (priv->contact_monitor)
-               g_object_unref (priv->contact_monitor);
-       priv->contact_monitor = NULL;
-
        g_queue_foreach (priv->messages_queue, (GFunc) g_object_unref, NULL);
        g_queue_clear (priv->messages_queue);
 
@@ -723,14 +820,14 @@ tp_chat_finalize (GObject *object)
 
        if (priv->properties) {
                for (i = 0; i < priv->properties->len; i++) {
-                       TpChatProperty *property;
+                       EmpathyTpChatProperty *property;
 
                        property = g_ptr_array_index (priv->properties, i);
                        g_free (property->name);
                        if (property->value) {
                                tp_g_value_slice_free (property->value);
                        }
-                       g_slice_free (TpChatProperty, property);
+                       g_slice_free (EmpathyTpChatProperty, property);
                }
                g_ptr_array_free (priv->properties, TRUE);
        }
@@ -742,28 +839,40 @@ tp_chat_finalize (GObject *object)
 }
 
 static void
-tp_chat_check_if_ready (EmpathyTpChat *chat)
+check_almost_ready (EmpathyTpChat *chat)
 {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
 
-       if (priv->ready || priv->user == NULL ||
-           (priv->members == NULL && priv->remote_contact == NULL)) {
+       if (priv->ready)
                return;
-       }
 
-       DEBUG ("Ready!");
+       if (priv->user == NULL)
+               return;
 
+       if (!priv->got_password_flags)
+               return;
+
+       /* We need either the members (room) or the remote contact (private chat).
+        * If the chat is protected by a password we can't get these information so
+        * consider the chat as ready so it can be presented to the user. */
+       if (!empathy_tp_chat_password_needed (chat) && priv->members == NULL &&
+           priv->remote_contact == NULL)
+               return;
+
+       tp_cli_channel_type_text_connect_to_received (priv->channel,
+                                                     tp_chat_received_cb,
+                                                     NULL, NULL,
+                                                     G_OBJECT (chat), NULL);
        priv->listing_pending_messages = TRUE;
+
+       /* TpChat will be ready once ListPendingMessages returned and all the messages
+        * have been added to the pending messages queue. */
        tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1,
                                                             FALSE,
                                                             tp_chat_list_pending_messages_cb,
                                                             NULL, NULL,
                                                             G_OBJECT (chat));
 
-       tp_cli_channel_type_text_connect_to_received (priv->channel,
-                                                     tp_chat_received_cb,
-                                                     NULL, NULL,
-                                                     G_OBJECT (chat), NULL);
        tp_cli_channel_type_text_connect_to_sent (priv->channel,
                                                  tp_chat_sent_cb,
                                                  NULL, NULL,
@@ -776,12 +885,6 @@ tp_chat_check_if_ready (EmpathyTpChat *chat)
                                                                           tp_chat_state_changed_cb,
                                                                           NULL, NULL,
                                                                           G_OBJECT (chat), NULL);
-       tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
-                                                                          tp_chat_state_changed_cb,
-                                                                          NULL, NULL,
-                                                                          G_OBJECT (chat), NULL);
-       priv->ready = TRUE;
-       g_object_notify (G_OBJECT (chat), "ready");
 }
 
 static void
@@ -799,6 +902,14 @@ tp_chat_update_remote_contact (EmpathyTpChat *chat)
                return;
        }
 
+       /* This is an MSN chat, but it's the new style where 1-1 chats don't
+        * have the group interface. If it has the conference interface, then
+        * it is indeed a MUC. */
+       if (tp_proxy_has_interface_by_id (priv->channel,
+                                         TP_IFACE_QUARK_CHANNEL_INTERFACE_CONFERENCE)) {
+               return;
+       }
+
        /* This is an MSN-like chat where anyone can join the chat at anytime.
         * If there is only one non-self contact member, we are in a private
         * chat and we set the "remote-contact" property to that contact. If
@@ -837,7 +948,7 @@ tp_chat_update_remote_contact (EmpathyTpChat *chat)
 }
 
 static void
-tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory,
+tp_chat_got_added_contacts_cb (TpConnection            *connection,
                               guint                    n_contacts,
                               EmpathyContact * const * contacts,
                               guint                    n_failed,
@@ -867,14 +978,125 @@ tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory,
                        priv->members = g_list_prepend (priv->members,
                                g_object_ref (contact));
                        g_signal_emit_by_name (chat, "members-changed",
-                                              contact, NULL, 0, NULL, FALSE);
+                                              contact, NULL, 0, NULL, TRUE);
                }
        }
 
        tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
-       tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
+       check_almost_ready (EMPATHY_TP_CHAT (chat));
+}
+
+static EmpathyContact *
+chat_lookup_contact (EmpathyTpChat *chat,
+                    TpHandle       handle,
+                    gboolean       remove_)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
+       GList *l;
+
+       for (l = priv->members; l; l = l->next) {
+               EmpathyContact *c = l->data;
+
+               if (empathy_contact_get_handle (c) != handle) {
+                       continue;
+               }
+
+               if (remove_) {
+                       /* Caller takes the reference. */
+                       priv->members = g_list_delete_link (priv->members, l);
+               } else {
+                       g_object_ref (c);
+               }
+
+               return c;
+       }
+
+       return NULL;
+}
+
+typedef struct
+{
+    TpHandle old_handle;
+    guint reason;
+    gchar *message;
+} ContactRenameData;
+
+static ContactRenameData *
+contact_rename_data_new (TpHandle handle,
+                        guint reason,
+                        const gchar* message)
+{
+       ContactRenameData *data = g_new (ContactRenameData, 1);
+       data->old_handle = handle;
+       data->reason = reason;
+       data->message = g_strdup (message);
+
+       return data;
+}
+
+static void
+contact_rename_data_free (ContactRenameData* data)
+{
+       g_free (data->message);
+       g_free (data);
 }
 
+static void
+tp_chat_got_renamed_contacts_cb (TpConnection            *connection,
+                                 guint                    n_contacts,
+                                 EmpathyContact * const * contacts,
+                                 guint                    n_failed,
+                                 const TpHandle          *failed,
+                                 const GError            *error,
+                                 gpointer                 user_data,
+                                 GObject                 *chat)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
+       const TpIntSet *members;
+       TpHandle handle;
+       EmpathyContact *old = NULL, *new = NULL;
+       ContactRenameData *rename_data = (ContactRenameData *) user_data;
+
+       if (error) {
+               DEBUG ("Error: %s", error->message);
+               return;
+       }
+
+       /* renamed members can only be delivered one at a time */
+       g_warn_if_fail (n_contacts == 1);
+
+       new = contacts[0];
+
+       members = tp_channel_group_get_members (priv->channel);
+       handle = empathy_contact_get_handle (new);
+
+       old = chat_lookup_contact (EMPATHY_TP_CHAT (chat),
+                                  rename_data->old_handle, TRUE);
+
+       /* Make sure the contact is still member */
+       if (tp_intset_is_member (members, handle)) {
+               priv->members = g_list_prepend (priv->members,
+                       g_object_ref (new));
+
+               if (old != NULL) {
+                       g_signal_emit_by_name (chat, "member-renamed",
+                                              old, new, rename_data->reason,
+                                              rename_data->message);
+                       g_object_unref (old);
+               }
+       }
+
+       if (priv->user == old) {
+               /* We change our nick */
+               g_object_unref (priv->user);
+               priv->user = g_object_ref (new);
+       }
+
+       tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
+       check_almost_ready (EMPATHY_TP_CHAT (chat));
+}
+
+
 static void
 tp_chat_group_members_changed_cb (TpChannel     *self,
                                  gchar         *message,
@@ -888,39 +1110,72 @@ tp_chat_group_members_changed_cb (TpChannel     *self,
 {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
        EmpathyContact *contact;
-       TpHandle handle;
+       EmpathyContact *actor_contact = NULL;
        guint i;
-       GList *l;
+       ContactRenameData *rename_data;
+       TpHandle old_handle;
+
+       /* Contact renamed */
+       if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
+               /* there can only be a single 'added' and a single 'removed' handle */
+               if (removed->len != 1 || added->len != 1) {
+                       g_warning ("RENAMED with %u added, %u removed (expected 1, 1)",
+                               added->len, removed->len);
+                       return;
+               }
+
+               old_handle = g_array_index (removed, guint, 0);
+
+               rename_data = contact_rename_data_new (old_handle, reason, message);
+               empathy_tp_contact_factory_get_from_handles (priv->connection,
+                       added->len, (TpHandle *) added->data,
+                       tp_chat_got_renamed_contacts_cb,
+                       rename_data, (GDestroyNotify) contact_rename_data_free,
+                       G_OBJECT (chat));
+               return;
+       }
+
+       if (actor != 0) {
+               actor_contact = chat_lookup_contact (chat, actor, FALSE);
+               if (actor_contact == NULL) {
+                       /* FIXME: handle this a tad more gracefully: perhaps
+                        * the actor was a server op. We could use the
+                        * contact-ids detail of MembersChangedDetailed.
+                        */
+                       DEBUG ("actor %u not a channel member", actor);
+               }
+       }
 
        /* Remove contacts that are not members anymore */
        for (i = 0; i < removed->len; i++) {
-               for (l = priv->members; l; l = l->next) {
-                       contact = l->data;
-                       handle = empathy_contact_get_handle (contact);
-                       if (handle == g_array_index (removed, TpHandle, i)) {
-                               priv->members = g_list_delete_link (priv->members, l);
-                               g_signal_emit_by_name (chat, "members-changed",
-                                                      contact, NULL, reason,
-                                                      message, FALSE);
-                               g_object_unref (contact);
-                               break;
-                       }
+               contact = chat_lookup_contact (chat,
+                       g_array_index (removed, TpHandle, i), TRUE);
+
+               if (contact != NULL) {
+                       g_signal_emit_by_name (chat, "members-changed", contact,
+                                              actor_contact, reason, message,
+                                              FALSE);
+                       g_object_unref (contact);
                }
        }
 
        /* Request added contacts */
        if (added->len > 0) {
-               empathy_tp_contact_factory_get_from_handles (priv->factory,
-                       added->len, (TpHandle*) added->data,
+               empathy_tp_contact_factory_get_from_handles (priv->connection,
+                       added->len, (TpHandle *) added->data,
                        tp_chat_got_added_contacts_cb, NULL, NULL,
                        G_OBJECT (chat));
        }
 
        tp_chat_update_remote_contact (chat);
+
+       if (actor_contact != NULL) {
+               g_object_unref (actor_contact);
+       }
 }
 
 static void
-tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
+tp_chat_got_remote_contact_cb (TpConnection            *connection,
                               EmpathyContact          *contact,
                               const GError            *error,
                               gpointer                 user_data,
@@ -930,18 +1185,18 @@ tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
 
        if (error) {
                DEBUG ("Error: %s", error->message);
-               empathy_tp_chat_close (EMPATHY_TP_CHAT (chat));
+               empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
                return;
        }
 
        priv->remote_contact = g_object_ref (contact);
        g_object_notify (chat, "remote-contact");
 
-       tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
+       check_almost_ready (EMPATHY_TP_CHAT (chat));
 }
 
 static void
-tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
+tp_chat_got_self_contact_cb (TpConnection            *connection,
                             EmpathyContact          *contact,
                             const GError            *error,
                             gpointer                 user_data,
@@ -951,13 +1206,51 @@ tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
 
        if (error) {
                DEBUG ("Error: %s", error->message);
-               empathy_tp_chat_close (EMPATHY_TP_CHAT (chat));
+               empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
                return;
        }
 
        priv->user = g_object_ref (contact);
        empathy_contact_set_is_user (priv->user, TRUE);
-       tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
+       check_almost_ready (EMPATHY_TP_CHAT (chat));
+}
+
+static void
+password_flags_changed_cb (TpChannel *channel,
+    guint added,
+    guint removed,
+    gpointer user_data,
+    GObject *weak_object)
+{
+       EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       gboolean was_needed, needed;
+
+       was_needed = empathy_tp_chat_password_needed (self);
+
+       priv->password_flags |= added;
+       priv->password_flags ^= removed;
+
+       needed = empathy_tp_chat_password_needed (self);
+
+       if (was_needed != needed)
+               g_object_notify (G_OBJECT (self), "password-needed");
+}
+
+static void
+got_password_flags_cb (TpChannel *proxy,
+                            guint password_flags,
+                            const GError *error,
+                            gpointer user_data,
+                            GObject *weak_object)
+{
+       EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+
+       priv->got_password_flags = TRUE;
+       priv->password_flags = password_flags;
+
+       check_almost_ready (EMPATHY_TP_CHAT (self));
 }
 
 static GObject *
@@ -967,18 +1260,19 @@ tp_chat_constructor (GType                  type,
 {
        GObject           *chat;
        EmpathyTpChatPriv *priv;
-       TpConnection      *connection;
        TpHandle           handle;
 
        chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
 
        priv = GET_PRIV (chat);
 
-       connection = tp_channel_borrow_connection (priv->channel);
-       priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
-       g_signal_connect (priv->channel, "invalidated",
+       priv->connection = g_object_ref (tp_account_get_connection (priv->account));
+       tp_g_signal_connect_object (priv->channel, "invalidated",
                          G_CALLBACK (tp_chat_invalidated_cb),
-                         chat);
+                         chat, 0);
+
+       g_assert (tp_proxy_is_prepared (priv->connection,
+               TP_CONNECTION_FEATURE_CAPABILITIES));
 
        if (tp_proxy_has_interface_by_id (priv->channel,
                                          TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
@@ -987,31 +1281,53 @@ tp_chat_constructor (GType                  type,
 
                /* Get self contact from the group's self handle */
                handle = tp_channel_group_get_self_handle (priv->channel);
-               empathy_tp_contact_factory_get_from_handle (priv->factory,
+               empathy_tp_contact_factory_get_from_handle (priv->connection,
                        handle, tp_chat_got_self_contact_cb,
                        NULL, NULL, chat);
 
                /* Get initial member contacts */
                members = tp_channel_group_get_members (priv->channel);
-               handles = tp_intset_to_array (members);         
-               empathy_tp_contact_factory_get_from_handles (priv->factory,
-                       handles->len, (TpHandle*) handles->data,
+               handles = tp_intset_to_array (members);
+               empathy_tp_contact_factory_get_from_handles (priv->connection,
+                       handles->len, (TpHandle *) handles->data,
                        tp_chat_got_added_contacts_cb, NULL, NULL, chat);
 
-               g_signal_connect (priv->channel, "group-members-changed",
-                       G_CALLBACK (tp_chat_group_members_changed_cb), chat);
+               priv->can_upgrade_to_muc = FALSE;
+
+               tp_g_signal_connect_object (priv->channel, "group-members-changed",
+                       G_CALLBACK (tp_chat_group_members_changed_cb), chat, 0);
        } else {
+               TpCapabilities *caps;
+               GPtrArray *classes;
+               guint i;
+
                /* Get the self contact from the connection's self handle */
-               handle = tp_connection_get_self_handle (connection);
-               empathy_tp_contact_factory_get_from_handle (priv->factory,
+               handle = tp_connection_get_self_handle (priv->connection);
+               empathy_tp_contact_factory_get_from_handle (priv->connection,
                        handle, tp_chat_got_self_contact_cb,
                        NULL, NULL, chat);
 
                /* Get the remote contact */
                handle = tp_channel_get_handle (priv->channel, NULL);
-               empathy_tp_contact_factory_get_from_handle (priv->factory,
+               empathy_tp_contact_factory_get_from_handle (priv->connection,
                        handle, tp_chat_got_remote_contact_cb,
                        NULL, NULL, chat);
+
+               caps = tp_connection_get_capabilities (priv->connection);
+               g_assert (caps != NULL);
+
+               classes = tp_capabilities_get_channel_classes (caps);
+
+               for (i = 0; i < classes->len; i++) {
+                       GValueArray *array = g_ptr_array_index (classes, i);
+                       const char **oprops = g_value_get_boxed (
+                               g_value_array_get_nth (array, 1));
+
+                       if (tp_strv_contains (oprops, TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS)) {
+                               priv->can_upgrade_to_muc = TRUE;
+                               break;
+                       }
+               }
        }
 
        if (tp_proxy_has_interface_by_id (priv->channel,
@@ -1030,6 +1346,22 @@ tp_chat_constructor (GType                  type,
                                                                               G_OBJECT (chat), NULL);
        }
 
+       /* Check if the chat is password protected */
+       if (tp_proxy_has_interface_by_id (priv->channel,
+                                         TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD)) {
+               priv->got_password_flags = FALSE;
+
+               tp_cli_channel_interface_password_connect_to_password_flags_changed
+                       (priv->channel, password_flags_changed_cb, chat, NULL,
+                        G_OBJECT (chat), NULL);
+
+               tp_cli_channel_interface_password_call_get_password_flags
+                       (priv->channel, -1, got_password_flags_cb, chat, NULL, chat);
+       } else {
+               /* No Password interface, so no need to fetch the password flags */
+               priv->got_password_flags = TRUE;
+       }
+
        return chat;
 }
 
@@ -1039,9 +1371,13 @@ tp_chat_get_property (GObject    *object,
                      GValue     *value,
                      GParamSpec *pspec)
 {
+       EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
        EmpathyTpChatPriv *priv = GET_PRIV (object);
 
        switch (param_id) {
+       case PROP_ACCOUNT:
+               g_value_set_object (value, priv->account);
+               break;
        case PROP_CHANNEL:
                g_value_set_object (value, priv->channel);
                break;
@@ -1051,6 +1387,9 @@ tp_chat_get_property (GObject    *object,
        case PROP_READY:
                g_value_set_boolean (value, priv->ready);
                break;
+       case PROP_PASSWORD_NEEDED:
+               g_value_set_boolean (value, empathy_tp_chat_password_needed (self));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -1066,6 +1405,9 @@ tp_chat_set_property (GObject      *object,
        EmpathyTpChatPriv *priv = GET_PRIV (object);
 
        switch (param_id) {
+       case PROP_ACCOUNT:
+               priv->account = g_value_dup_object (value);
+               break;
        case PROP_CHANNEL:
                priv->channel = g_value_dup_object (value);
                break;
@@ -1086,6 +1428,16 @@ empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
        object_class->get_property = tp_chat_get_property;
        object_class->set_property = tp_chat_set_property;
 
+       g_object_class_install_property (object_class,
+                                        PROP_ACCOUNT,
+                                        g_param_spec_object ("account",
+                                                             "TpAccount",
+                                                             "the account associated with the chat",
+                                                             TP_TYPE_ACCOUNT,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
        g_object_class_install_property (object_class,
                                         PROP_CHANNEL,
                                         g_param_spec_object ("channel",
@@ -1111,6 +1463,14 @@ empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
                                                               FALSE,
                                                               G_PARAM_READABLE));
 
+       g_object_class_install_property (object_class,
+                                        PROP_PASSWORD_NEEDED,
+                                        g_param_spec_boolean ("password-needed",
+                                                              "password needed",
+                                                              "TRUE if a password is needed to join the channel",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+
        /* Signals */
        signals[MESSAGE_RECEIVED] =
                g_signal_new ("message-received",
@@ -1128,9 +1488,9 @@ empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
                              G_SIGNAL_RUN_LAST,
                              0,
                              NULL, NULL,
-                             _empathy_marshal_VOID__OBJECT_UINT,
+                             _empathy_marshal_VOID__STRING_UINT,
                              G_TYPE_NONE,
-                             2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
+                             2, G_TYPE_STRING, G_TYPE_UINT);
 
        signals[CHAT_STATE_CHANGED] =
                g_signal_new ("chat-state-changed",
@@ -1172,7 +1532,6 @@ empathy_tp_chat_init (EmpathyTpChat *chat)
                EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv);
 
        chat->priv = priv;
-       priv->contact_monitor = NULL;
        priv->messages_queue = g_queue_new ();
        priv->pending_messages_queue = g_queue_new ();
 }
@@ -1183,18 +1542,22 @@ tp_chat_iface_init (EmpathyContactListIface *iface)
        iface->add         = tp_chat_add;
        iface->remove      = tp_chat_remove;
        iface->get_members = tp_chat_get_members;
-       iface->get_monitor = tp_chat_get_monitor;
 }
 
 EmpathyTpChat *
-empathy_tp_chat_new (TpChannel *channel)
+empathy_tp_chat_new (TpAccount *account,
+                    TpChannel *channel)
 {
+       g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+       g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
+
        return g_object_new (EMPATHY_TYPE_TP_CHAT,
+                            "account", account,
                             "channel", channel,
                             NULL);
 }
 
-void
+static void
 empathy_tp_chat_close (EmpathyTpChat *chat) {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
 
@@ -1208,10 +1571,19 @@ const gchar *
 empathy_tp_chat_get_id (EmpathyTpChat *chat)
 {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
+       const gchar *id;
+
 
        g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
 
-       return tp_channel_get_identifier (priv->channel);
+       id = tp_channel_get_identifier (priv->channel);
+       if (!EMP_STR_EMPTY (id))
+               return id;
+       else if (priv->remote_contact)
+               return empathy_contact_get_id (priv->remote_contact);
+       else
+               return NULL;
+
 }
 
 EmpathyContact *
@@ -1235,6 +1607,16 @@ empathy_tp_chat_get_channel (EmpathyTpChat *chat)
        return priv->channel;
 }
 
+TpAccount *
+empathy_tp_chat_get_account (EmpathyTpChat *chat)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
+
+       g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
+
+       return priv->account;
+}
+
 TpConnection *
 empathy_tp_chat_get_connection (EmpathyTpChat *chat)
 {
@@ -1244,7 +1626,6 @@ empathy_tp_chat_get_connection (EmpathyTpChat *chat)
 
        return tp_channel_borrow_connection (priv->channel);
 }
-
 gboolean
 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
 {
@@ -1289,13 +1670,16 @@ empathy_tp_chat_set_state (EmpathyTpChat      *chat,
        g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
        g_return_if_fail (priv->ready);
 
-       DEBUG ("Set state: %d", state);
-       tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
-                                                                state,
-                                                                tp_chat_async_cb,
-                                                                "setting chat state",
-                                                                NULL,
-                                                                G_OBJECT (chat));
+       if (tp_proxy_has_interface_by_id (priv->channel,
+                                         TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE)) {
+               DEBUG ("Set state: %d", state);
+               tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
+                                                                        state,
+                                                                        tp_chat_async_cb,
+                                                                        "setting chat state",
+                                                                        NULL,
+                                                                        G_OBJECT (chat));
+       }
 }
 
 
@@ -1330,7 +1714,7 @@ empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
        g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
        g_return_if_fail (priv->ready);
 
-       if (empathy_message_get_sender (message) == priv->user)
+       if (!empathy_message_is_incoming (message))
                goto out;
 
        message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
@@ -1349,25 +1733,25 @@ out:
 
 void
 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
-                                     const GList *messages) {
+                                     const GSList *messages) {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
        /* Copy messages as the messges list (probably is) our own */
-       GList *msgs = g_list_copy ((GList *) messages);
-       GList *l;
+       GSList *msgs = g_slist_copy ((GSList *) messages);
+       GSList *l;
        guint length;
        GArray *message_ids;
 
        g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
        g_return_if_fail (priv->ready);
 
-       length = g_list_length ((GList *)messages);
+       length = g_slist_length ((GSList *) messages);
 
        if (length == 0)
                return;
 
        message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
 
-       for (l = msgs; l != NULL; l = g_list_next (l)) {
+       for (l = msgs; l != NULL; l = g_slist_next (l)) {
                GList *m;
 
                EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
@@ -1376,7 +1760,7 @@ empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
                g_assert (m != NULL);
                g_queue_delete_link (priv->pending_messages_queue, m);
 
-               if (empathy_message_get_sender (message) != priv->user) {
+               if (empathy_message_is_incoming (message)) {
                        guint id = empathy_message_get_id (message);
                        g_array_append_val (message_ids, id);
                }
@@ -1387,6 +1771,191 @@ empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
                acknowledge_messages (chat, message_ids);
 
        g_array_free (message_ids, TRUE);
-       g_list_free (msgs);
+       g_slist_free (msgs);
+}
+
+void
+empathy_tp_chat_acknowledge_all_messages (EmpathyTpChat *chat)
+{
+  empathy_tp_chat_acknowledge_messages (chat,
+    (GSList *) empathy_tp_chat_get_pending_messages (chat));
+}
+
+gboolean
+empathy_tp_chat_password_needed (EmpathyTpChat *self)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+
+       return priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE;
+}
+
+static void
+provide_password_cb (TpChannel *channel,
+                                     gboolean correct,
+                                     const GError *error,
+                                     gpointer user_data,
+                                     GObject *weak_object)
+{
+       GSimpleAsyncResult *result = user_data;
+
+       if (error != NULL) {
+               g_simple_async_result_set_from_error (result, error);
+       }
+       else if (!correct) {
+               /* The current D-Bus API is a bit weird so re-use the
+                * AuthenticationFailed error */
+               g_simple_async_result_set_error (result, TP_ERRORS,
+                                                TP_ERROR_AUTHENTICATION_FAILED, "Wrong password");
+       }
+
+       g_simple_async_result_complete (result);
+       g_object_unref (result);
+}
+
+void
+empathy_tp_chat_provide_password_async (EmpathyTpChat *self,
+                                                    const gchar *password,
+                                                    GAsyncReadyCallback callback,
+                                                    gpointer user_data)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       GSimpleAsyncResult *result;
+
+       result = g_simple_async_result_new (G_OBJECT (self),
+                                           callback, user_data,
+                                           empathy_tp_chat_provide_password_finish);
+
+       tp_cli_channel_interface_password_call_provide_password
+               (priv->channel, -1, password, provide_password_cb, result,
+                NULL, G_OBJECT (self));
+}
+
+gboolean
+empathy_tp_chat_provide_password_finish (EmpathyTpChat *self,
+                                                     GAsyncResult *result,
+                                                     GError **error)
+{
+       if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+               error))
+               return FALSE;
+
+       g_return_val_if_fail (g_simple_async_result_is_valid (result,
+                                                             G_OBJECT (self), empathy_tp_chat_provide_password_finish), FALSE);
+
+       return TRUE;
 }
 
+/**
+ * empathy_tp_chat_can_add_contact:
+ *
+ * Returns: %TRUE if empathy_contact_list_add() will work for this channel.
+ * That is if this chat is a 1-to-1 channel that can be upgraded to
+ * a MUC using the Conference interface or if the channel is a MUC.
+ */
+gboolean
+empathy_tp_chat_can_add_contact (EmpathyTpChat *self)
+{
+       EmpathyTpChatPriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
+
+       priv = GET_PRIV (self);
+
+       return priv->can_upgrade_to_muc ||
+               tp_proxy_has_interface_by_id (priv->channel,
+                       TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);;
+}
+
+static void
+leave_remove_members_cb (TpChannel *proxy,
+                        const GError *error,
+                        gpointer user_data,
+                        GObject *weak_object)
+{
+       EmpathyTpChat *self = user_data;
+
+       if (error == NULL)
+               return;
+
+       DEBUG ("RemoveMembers failed (%s); closing the channel", error->message);
+       empathy_tp_chat_close (self);
+}
+
+void
+empathy_tp_chat_leave (EmpathyTpChat *self)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       TpHandle self_handle;
+       GArray *array;
+
+       if (!tp_proxy_has_interface_by_id (priv->channel,
+               TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
+               empathy_tp_chat_close (self);
+               return;
+       }
+
+       self_handle = tp_channel_group_get_self_handle (priv->channel);
+       if (self_handle == 0) {
+               /* we are not member of the channel */
+               empathy_tp_chat_close (self);
+               return;
+       }
+
+       array = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
+       g_array_insert_val (array, 0, self_handle);
+
+       tp_cli_channel_interface_group_call_remove_members (priv->channel, -1, array,
+               "", leave_remove_members_cb, self, NULL, G_OBJECT (self));
+
+       g_array_free (array, TRUE);
+}
+
+static void
+add_members_cb (TpChannel *proxy,
+               const GError *error,
+               gpointer user_data,
+               GObject *weak_object)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (weak_object);
+
+       if (error != NULL) {
+               DEBUG ("Failed to join chat (%s): %s",
+                       tp_channel_get_identifier (priv->channel), error->message);
+       }
+}
+
+void
+empathy_tp_chat_join (EmpathyTpChat *self)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       TpHandle self_handle;
+       GArray *members;
+
+       self_handle = tp_channel_group_get_self_handle (priv->channel);
+
+       members = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
+       g_array_append_val (members, self_handle);
+
+       tp_cli_channel_interface_group_call_add_members (priv->channel, -1, members,
+               "", add_members_cb, NULL, NULL, G_OBJECT (self));
+
+       g_array_free (members, TRUE);
+}
+
+gboolean
+empathy_tp_chat_is_invited (EmpathyTpChat *self,
+                           TpHandle *inviter)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       TpHandle self_handle;
+
+       if (!tp_proxy_has_interface (priv->channel, TP_IFACE_CHANNEL_INTERFACE_GROUP))
+               return FALSE;
+
+       self_handle = tp_channel_group_get_self_handle (priv->channel);
+       if (self_handle == 0)
+               return FALSE;
+
+       return tp_channel_group_get_local_pending_info (priv->channel, self_handle,
+               inviter, NULL, NULL);
+}