]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-tp-chat.c
Merge branch 'people-nearby-fake-group-613558'
[empathy.git] / libempathy / empathy-tp-chat.c
index 7429e1ddc85f69b54f64fd8893dcaaf34ae30e0f..83582741008d486dc97a45ab4f2a8751aa2808d5 100644 (file)
 
 #include <string.h>
 
-#include <telepathy-glib/channel.h>
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/util.h>
-#include <telepathy-glib/interfaces.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-dispatcher.h"
 #include "empathy-marshal.h"
 #include "empathy-time.h"
 #include "empathy-utils.h"
@@ -55,22 +55,21 @@ 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_CHANNEL,
        PROP_REMOTE_CONTACT,
+       PROP_PASSWORD_NEEDED,
        PROP_READY,
 };
 
@@ -119,17 +118,54 @@ 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) {
+               EmpathyDispatcher *dispatcher;
+               TpConnection      *connection;
+               GHashTable        *props;
+               const char        *object_path;
+               GPtrArray          channels = { (gpointer *) &object_path, 1 };
+               const char        *invitees[2] = { NULL, };
+
+               dispatcher = empathy_dispatcher_dup_singleton ();
+               connection = tp_channel_borrow_connection (priv->channel);
+
+               invitees[0] = empathy_contact_get_id (contact);
+               object_path = tp_proxy_get_object_path (priv->channel);
+
+               props = tp_asv_new (
+                   TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING,
+                       TP_IFACE_CHANNEL_TYPE_TEXT,
+                   TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT,
+                       TP_HANDLE_TYPE_NONE,
+                   EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels",
+                       TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels,
+                   EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs",
+                       G_TYPE_STRV, invitees,
+                   /* FIXME: InvitationMessage ? */
+                   NULL);
+
+               /* Although this is a MUC, it's anonymous, so CreateChannel is
+                * valid.
+                * props now belongs to EmpathyDispatcher, don't free it */
+               empathy_dispatcher_create_channel (dispatcher, connection,
+                               props, NULL, NULL);
+
+               g_object_unref (dispatcher);
+       } else {
+               g_warning ("Cannot add to this channel");
+       }
 }
 
 static void
@@ -164,7 +200,8 @@ 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;
@@ -227,11 +264,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;
@@ -243,6 +282,9 @@ 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);
+
        g_queue_push_tail (priv->messages_queue, message);
 
        if (from_handle == 0) {
@@ -294,11 +336,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
@@ -318,11 +362,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
@@ -456,11 +502,13 @@ 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) {
@@ -486,10 +534,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));
@@ -524,10 +572,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));
@@ -590,11 +638,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));
@@ -621,9 +669,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);
@@ -665,6 +717,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)
 {
@@ -720,14 +802,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);
        }
@@ -743,10 +825,21 @@ tp_chat_check_if_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;
+
+       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;
-       }
 
        DEBUG ("Ready!");
 
@@ -773,10 +866,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");
 }
@@ -796,6 +885,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,
+                                         EMP_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
@@ -875,7 +972,7 @@ tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory,
 static EmpathyContact *
 chat_lookup_contact (EmpathyTpChat *chat,
                     TpHandle       handle,
-                    gboolean       remove)
+                    gboolean       remove_)
 {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
        GList *l;
@@ -887,7 +984,7 @@ chat_lookup_contact (EmpathyTpChat *chat,
                        continue;
                }
 
-               if (remove) {
+               if (remove_) {
                        /* Caller takes the reference. */
                        priv->members = g_list_delete_link (priv->members, l);
                } else {
@@ -900,6 +997,89 @@ chat_lookup_contact (EmpathyTpChat *chat,
        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 (EmpathyTpContactFactory *factory,
+                                 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));
+       tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
+}
+
+
 static void
 tp_chat_group_members_changed_cb (TpChannel     *self,
                                  gchar         *message,
@@ -915,6 +1095,25 @@ tp_chat_group_members_changed_cb (TpChannel     *self,
        EmpathyContact *contact;
        EmpathyContact *actor_contact = NULL;
        guint i;
+       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 */
+               g_warn_if_fail (removed->len == 1);
+               g_warn_if_fail (added->len == 1);
+
+               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->factory,
+                       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);
@@ -966,7 +1165,7 @@ 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;
        }
 
@@ -987,7 +1186,7 @@ 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;
        }
 
@@ -996,6 +1195,44 @@ tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
        tp_chat_check_if_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;
+
+       tp_chat_check_if_ready (EMPATHY_TP_CHAT (self));
+}
+
 static GObject *
 tp_chat_constructor (GType                  type,
                     guint                  n_props,
@@ -1034,9 +1271,14 @@ tp_chat_constructor (GType                  type,
                        handles->len, (TpHandle *) handles->data,
                        tp_chat_got_added_contacts_cb, NULL, NULL, chat);
 
+               priv->can_upgrade_to_muc = FALSE;
+
                g_signal_connect (priv->channel, "group-members-changed",
                        G_CALLBACK (tp_chat_group_members_changed_cb), chat);
        } else {
+               EmpathyDispatcher *dispatcher = empathy_dispatcher_dup_singleton ();
+               GList *list, *ptr;
+
                /* 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,
@@ -1048,6 +1290,25 @@ tp_chat_constructor (GType                  type,
                empathy_tp_contact_factory_get_from_handle (priv->factory,
                        handle, tp_chat_got_remote_contact_cb,
                        NULL, NULL, chat);
+
+               list = empathy_dispatcher_find_requestable_channel_classes (
+                       dispatcher, connection,
+                       tp_channel_get_channel_type (priv->channel),
+                       TP_UNKNOWN_HANDLE_TYPE, NULL);
+
+               for (ptr = list; ptr; ptr = ptr->next) {
+                       GValueArray *array = ptr->data;
+                       const char **oprops = g_value_get_boxed (
+                               g_value_array_get_nth (array, 1));
+
+                       if (tp_strv_contains (oprops, EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels")) {
+                               priv->can_upgrade_to_muc = TRUE;
+                               break;
+                       }
+               }
+
+               g_list_free (list);
+               g_object_unref (dispatcher);
        }
 
        if (tp_proxy_has_interface_by_id (priv->channel,
@@ -1066,6 +1327,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;
 }
 
@@ -1075,6 +1352,7 @@ tp_chat_get_property (GObject    *object,
                      GValue     *value,
                      GParamSpec *pspec)
 {
+       EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
        EmpathyTpChatPriv *priv = GET_PRIV (object);
 
        switch (param_id) {
@@ -1087,6 +1365,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;
@@ -1147,6 +1428,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",
@@ -1230,7 +1519,7 @@ empathy_tp_chat_new (TpChannel *channel)
                             NULL);
 }
 
-void
+static void
 empathy_tp_chat_close (EmpathyTpChat *chat) {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
 
@@ -1244,10 +1533,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 *
@@ -1369,7 +1667,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);
@@ -1388,25 +1686,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);
@@ -1415,7 +1713,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);
                }
@@ -1426,6 +1724,141 @@ 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);
+}