1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Authors: Xavier Claessens <xclaesse@gmail.com>
27 #include <libtelepathy/tp-chan-type-text-gen.h>
28 #include <libtelepathy/tp-chan-iface-chat-state-gen.h>
29 #include <libtelepathy/tp-conn.h>
30 #include <libtelepathy/tp-helpers.h>
31 #include <libtelepathy/tp-props-iface.h>
33 #include "empathy-tp-chat.h"
34 #include "empathy-contact-manager.h"
35 #include "empathy-tp-contact-list.h"
36 #include "empathy-marshal.h"
37 #include "empathy-debug.h"
38 #include "empathy-time.h"
39 #include "empathy-utils.h"
41 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
42 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv))
44 #define DEBUG_DOMAIN "TpChat"
46 struct _EmpathyTpChatPriv {
47 EmpathyTpContactList *list;
48 EmpathyContactManager *manager;
55 DBusGProxy *props_iface;
56 DBusGProxy *text_iface;
57 DBusGProxy *chat_state_iface;
60 static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass);
61 static void empathy_tp_chat_init (EmpathyTpChat *chat);
62 static void tp_chat_finalize (GObject *object);
63 static GObject * tp_chat_constructor (GType type,
65 GObjectConstructParam *props);
66 static void tp_chat_get_property (GObject *object,
70 static void tp_chat_set_property (GObject *object,
74 static void tp_chat_destroy_cb (TpChan *text_chan,
76 static void tp_chat_closed_cb (TpChan *text_chan,
78 static void tp_chat_received_cb (DBusGProxy *text_iface,
86 static void tp_chat_sent_cb (DBusGProxy *text_iface,
91 static void tp_chat_send_error_cb (DBusGProxy *text_iface,
97 static void tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
99 TelepathyChannelChatState state,
100 EmpathyTpChat *chat);
101 static EmpathyMessage * tp_chat_build_message (EmpathyTpChat *chat,
105 const gchar *message_body);
106 static void tp_chat_properties_ready_cb (TpPropsIface *props_iface,
107 EmpathyTpChat *chat);
108 static void tp_chat_properties_changed_cb (TpPropsIface *props_iface,
111 EmpathyTpChat *chat);
126 PROP_PASSWORD_REQUIRED,
130 PROP_SUBJECT_CONTACT,
131 PROP_SUBJECT_TIMESTAMP
142 static guint signals[LAST_SIGNAL];
144 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
147 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
149 GObjectClass *object_class = G_OBJECT_CLASS (klass);
151 object_class->finalize = tp_chat_finalize;
152 object_class->constructor = tp_chat_constructor;
153 object_class->get_property = tp_chat_get_property;
154 object_class->set_property = tp_chat_set_property;
156 /* Construct-only properties */
157 g_object_class_install_property (object_class,
159 g_param_spec_object ("account",
161 "The account associated with the channel",
164 G_PARAM_CONSTRUCT_ONLY));
165 g_object_class_install_property (object_class,
167 g_param_spec_object ("tp-chan",
169 "The text channel for the chat",
172 G_PARAM_CONSTRUCT_ONLY));
174 /* Normal properties */
175 g_object_class_install_property (object_class,
177 g_param_spec_boolean ("acknowledge",
183 /* Properties of Text Channel */
184 g_object_class_install_property (object_class,
186 g_param_spec_boolean ("anonymous",
191 g_object_class_install_property (object_class,
193 g_param_spec_boolean ("invite-only",
198 g_object_class_install_property (object_class,
200 g_param_spec_uint ("limit",
207 g_object_class_install_property (object_class,
209 g_param_spec_boolean ("limited",
214 g_object_class_install_property (object_class,
216 g_param_spec_boolean ("moderated",
221 g_object_class_install_property (object_class,
223 g_param_spec_string ("name",
228 g_object_class_install_property (object_class,
230 g_param_spec_string ("description",
235 g_object_class_install_property (object_class,
237 g_param_spec_string ("password",
242 g_object_class_install_property (object_class,
243 PROP_PASSWORD_REQUIRED,
244 g_param_spec_boolean ("password-required",
249 g_object_class_install_property (object_class,
251 g_param_spec_boolean ("persistent",
256 g_object_class_install_property (object_class,
258 g_param_spec_boolean ("private",
264 g_object_class_install_property (object_class,
266 g_param_spec_string ("subject",
271 g_object_class_install_property (object_class,
272 PROP_SUBJECT_CONTACT,
273 g_param_spec_uint ("subject-contact",
280 g_object_class_install_property (object_class,
281 PROP_SUBJECT_TIMESTAMP,
282 g_param_spec_uint ("subject-timestamp",
291 signals[MESSAGE_RECEIVED] =
292 g_signal_new ("message-received",
293 G_TYPE_FROM_CLASS (klass),
297 g_cclosure_marshal_VOID__OBJECT,
299 1, EMPATHY_TYPE_MESSAGE);
301 signals[SEND_ERROR] =
302 g_signal_new ("send-error",
303 G_TYPE_FROM_CLASS (klass),
307 empathy_marshal_VOID__OBJECT_UINT,
309 2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
311 signals[CHAT_STATE_CHANGED] =
312 g_signal_new ("chat-state-changed",
313 G_TYPE_FROM_CLASS (klass),
317 empathy_marshal_VOID__OBJECT_UINT,
319 2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
322 g_signal_new ("destroy",
323 G_TYPE_FROM_CLASS (klass),
327 g_cclosure_marshal_VOID__VOID,
331 g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
335 empathy_tp_chat_init (EmpathyTpChat *chat)
341 tp_chat_finalize (GObject *object)
343 EmpathyTpChatPriv *priv;
345 GError *error = NULL;
347 chat = EMPATHY_TP_CHAT (object);
348 priv = GET_PRIV (chat);
351 g_signal_handlers_disconnect_by_func (priv->tp_chan,
354 if (priv->acknowledge) {
355 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
356 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
357 empathy_debug (DEBUG_DOMAIN,
358 "Error closing text channel: %s",
359 error ? error->message : "No error given");
360 g_clear_error (&error);
363 g_object_unref (priv->tp_chan);
367 g_object_unref (priv->manager);
370 g_object_unref (priv->list);
373 g_object_unref (priv->account);
376 g_object_unref (priv->mc);
380 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
384 tp_chat_constructor (GType type,
386 GObjectConstructParam *props)
389 EmpathyTpChatPriv *priv;
391 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
393 priv = GET_PRIV (chat);
395 priv->manager = empathy_contact_manager_new ();
396 priv->list = empathy_contact_manager_get_list (priv->manager, priv->account);
397 priv->mc = empathy_mission_control_new ();
398 g_object_ref (priv->list);
400 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
401 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
402 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
403 TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
404 priv->props_iface = tp_chan_get_interface (priv->tp_chan,
405 TELEPATHY_PROPS_IFACE_QUARK);
407 g_signal_connect (priv->tp_chan, "destroy",
408 G_CALLBACK (tp_chat_destroy_cb),
410 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
411 G_CALLBACK (tp_chat_closed_cb),
413 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
414 G_CALLBACK (tp_chat_received_cb),
416 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
417 G_CALLBACK (tp_chat_sent_cb),
419 dbus_g_proxy_connect_signal (priv->text_iface, "SendError",
420 G_CALLBACK (tp_chat_send_error_cb),
423 if (priv->chat_state_iface != NULL) {
424 dbus_g_proxy_connect_signal (priv->chat_state_iface,
426 G_CALLBACK (tp_chat_state_changed_cb),
429 if (priv->props_iface != NULL) {
430 tp_props_iface_set_mapping (TELEPATHY_PROPS_IFACE (priv->props_iface),
431 "anonymous", PROP_ANONYMOUS,
432 "invite-only", PROP_INVITE_ONLY,
434 "limited", PROP_LIMITED,
435 "moderated", PROP_MODERATED,
437 "description", PROP_DESCRIPTION,
438 "password", PROP_PASSWORD,
439 "password-required", PROP_PASSWORD_REQUIRED,
440 "persistent", PROP_PERSISTENT,
441 "private", PROP_PRIVATE,
442 "subject", PROP_SUBJECT,
443 "subject-contact", PROP_SUBJECT_CONTACT,
444 "subject-timestamp", PROP_SUBJECT_TIMESTAMP,
446 g_signal_connect (priv->props_iface, "properties-ready",
447 G_CALLBACK (tp_chat_properties_ready_cb),
449 g_signal_connect (priv->props_iface, "properties-changed",
450 G_CALLBACK (tp_chat_properties_changed_cb),
458 tp_chat_get_property (GObject *object,
463 EmpathyTpChatPriv *priv;
466 priv = GET_PRIV (object);
467 chat = EMPATHY_TP_CHAT (object);
469 if (param_id >= PROP_ANONYMOUS &&
470 param_id <= PROP_SUBJECT_TIMESTAMP) {
471 if (priv->props_iface) {
472 tp_props_iface_get_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
482 g_value_set_object (value, priv->account);
485 g_value_set_object (value, priv->tp_chan);
487 case PROP_ACKNOWLEDGE:
488 g_value_set_boolean (value, priv->acknowledge);
491 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
497 tp_chat_set_property (GObject *object,
502 EmpathyTpChatPriv *priv;
505 priv = GET_PRIV (object);
506 chat = EMPATHY_TP_CHAT (object);
508 if (param_id >= PROP_ANONYMOUS &&
509 param_id <= PROP_SUBJECT_TIMESTAMP) {
510 if (priv->props_iface) {
511 tp_props_iface_set_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
521 priv->account = g_object_ref (g_value_get_object (value));
524 priv->tp_chan = g_object_ref (g_value_get_object (value));
526 case PROP_ACKNOWLEDGE:
527 empathy_tp_chat_set_acknowledge (EMPATHY_TP_CHAT (object),
528 g_value_get_boolean (value));
531 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
537 empathy_tp_chat_new (McAccount *account,
540 return g_object_new (EMPATHY_TYPE_TP_CHAT,
547 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
554 const gchar *bus_name;
557 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
559 mc = empathy_mission_control_new ();
560 account = empathy_contact_get_account (contact);
562 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
563 /* The account is not connected, nothing to do. */
567 tp_conn = mission_control_get_connection (mc, account, NULL);
568 g_return_val_if_fail (tp_conn != NULL, NULL);
569 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
570 handle = empathy_contact_get_handle (contact);
572 text_chan = tp_conn_new_channel (tp_get_bus (),
575 TP_IFACE_CHANNEL_TYPE_TEXT,
576 TP_HANDLE_TYPE_CONTACT,
580 chat = empathy_tp_chat_new (account, text_chan);
582 g_object_unref (tp_conn);
583 g_object_unref (text_chan);
590 empathy_tp_chat_get_acknowledge (EmpathyTpChat *chat)
592 EmpathyTpChatPriv *priv;
594 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
596 priv = GET_PRIV (chat);
598 return priv->acknowledge;
602 empathy_tp_chat_set_acknowledge (EmpathyTpChat *chat,
603 gboolean acknowledge)
605 EmpathyTpChatPriv *priv;
607 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
609 priv = GET_PRIV (chat);
611 priv->acknowledge = acknowledge;
612 g_object_notify (G_OBJECT (chat), "acknowledge");
616 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
618 EmpathyTpChatPriv *priv;
620 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
622 priv = GET_PRIV (chat);
624 return priv->tp_chan;
628 empathy_tp_chat_get_pendings (EmpathyTpChat *chat)
630 EmpathyTpChatPriv *priv;
631 GPtrArray *messages_list;
633 GList *messages = NULL;
634 GError *error = NULL;
636 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
638 priv = GET_PRIV (chat);
640 /* If we do this call async, don't forget to ignore Received signal
641 * until we get the answer */
642 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
646 empathy_debug (DEBUG_DOMAIN,
647 "Error retrieving pending messages: %s",
648 error ? error->message : "No error given");
649 g_clear_error (&error);
653 for (i = 0; i < messages_list->len; i++) {
654 EmpathyMessage *message;
655 GValueArray *message_struct;
656 const gchar *message_body;
663 message_struct = g_ptr_array_index (messages_list, i);
665 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
666 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
667 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
668 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
669 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
670 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
672 empathy_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
674 message = tp_chat_build_message (chat,
680 messages = g_list_prepend (messages, message);
682 g_value_array_free (message_struct);
684 messages = g_list_reverse (messages);
686 g_ptr_array_free (messages_list, TRUE);
692 empathy_tp_chat_send (EmpathyTpChat *chat,
693 EmpathyMessage *message)
695 EmpathyTpChatPriv *priv;
696 const gchar *message_body;
697 EmpathyMessageType message_type;
698 GError *error = NULL;
700 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
701 g_return_if_fail (EMPATHY_IS_MESSAGE (message));
703 priv = GET_PRIV (chat);
705 message_body = empathy_message_get_body (message);
706 message_type = empathy_message_get_type (message);
708 empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
709 if (!tp_chan_type_text_send (priv->text_iface,
713 empathy_debug (DEBUG_DOMAIN,
715 error ? error->message : "No error given");
716 g_clear_error (&error);
721 empathy_tp_chat_set_state (EmpathyTpChat *chat,
722 TelepathyChannelChatState state)
724 EmpathyTpChatPriv *priv;
725 GError *error = NULL;
727 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
729 priv = GET_PRIV (chat);
731 if (priv->chat_state_iface) {
732 empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
733 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
736 empathy_debug (DEBUG_DOMAIN,
737 "Set Chat State Error: %s",
738 error ? error->message : "No error given");
739 g_clear_error (&error);
745 empathy_tp_chat_get_id (EmpathyTpChat *chat)
747 EmpathyTpChatPriv *priv;
749 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
751 priv = GET_PRIV (chat);
757 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
763 tp_chat_destroy_cb (TpChan *text_chan,
766 EmpathyTpChatPriv *priv;
768 priv = GET_PRIV (chat);
770 empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
772 g_object_unref (priv->tp_chan);
773 priv->tp_chan = NULL;
774 priv->text_iface = NULL;
775 priv->chat_state_iface = NULL;
776 priv->props_iface = NULL;
778 g_signal_emit (chat, signals[DESTROY], 0);
782 tp_chat_closed_cb (TpChan *text_chan,
785 EmpathyTpChatPriv *priv;
787 priv = GET_PRIV (chat);
789 /* The channel is closed, do just like if the proxy was destroyed */
790 g_signal_handlers_disconnect_by_func (priv->tp_chan,
793 tp_chat_destroy_cb (text_chan, chat);
797 tp_chat_received_cb (DBusGProxy *text_iface,
806 EmpathyTpChatPriv *priv;
807 EmpathyMessage *message;
809 priv = GET_PRIV (chat);
811 empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
813 message = tp_chat_build_message (chat,
819 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
820 g_object_unref (message);
822 if (priv->acknowledge) {
825 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
826 g_array_append_val (message_ids, message_id);
827 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
829 g_array_free (message_ids, TRUE);
834 tp_chat_sent_cb (DBusGProxy *text_iface,
840 EmpathyMessage *message;
842 empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
844 message = tp_chat_build_message (chat,
850 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
851 g_object_unref (message);
855 tp_chat_send_error_cb (DBusGProxy *text_iface,
862 EmpathyMessage *message;
864 empathy_debug (DEBUG_DOMAIN, "Message sent error: %s (%d)",
865 message_body, error_code);
867 message = tp_chat_build_message (chat,
873 g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
874 g_object_unref (message);
878 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
880 TelepathyChannelChatState state,
883 EmpathyTpChatPriv *priv;
884 EmpathyContact *contact;
886 priv = GET_PRIV (chat);
888 contact = empathy_tp_contact_list_get_from_handle (priv->list, handle);
890 empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
891 empathy_contact_get_name (contact),
895 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
896 g_object_unref (contact);
899 static EmpathyMessage *
900 tp_chat_build_message (EmpathyTpChat *chat,
904 const gchar *message_body)
906 EmpathyTpChatPriv *priv;
907 EmpathyMessage *message;
908 EmpathyContact *sender;
909 EmpathyContact *receiver;
911 priv = GET_PRIV (chat);
913 receiver = empathy_tp_contact_list_get_user (priv->list);
914 if (from_handle == 0) {
915 sender = g_object_ref (receiver);
917 sender = empathy_tp_contact_list_get_from_handle (priv->list,
921 message = empathy_message_new (message_body);
922 empathy_message_set_type (message, type);
923 empathy_message_set_sender (message, sender);
924 empathy_message_set_receiver (message, receiver);
925 empathy_message_set_timestamp (message, (EmpathyTime) timestamp);
927 g_object_unref (sender);
933 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
936 g_object_notify (G_OBJECT (chat), "anonymous");
937 g_object_notify (G_OBJECT (chat), "invite-only");
938 g_object_notify (G_OBJECT (chat), "limit");
939 g_object_notify (G_OBJECT (chat), "limited");
940 g_object_notify (G_OBJECT (chat), "moderated");
941 g_object_notify (G_OBJECT (chat), "name");
942 g_object_notify (G_OBJECT (chat), "description");
943 g_object_notify (G_OBJECT (chat), "password");
944 g_object_notify (G_OBJECT (chat), "password-required");
945 g_object_notify (G_OBJECT (chat), "persistent");
946 g_object_notify (G_OBJECT (chat), "private");
947 g_object_notify (G_OBJECT (chat), "subject");
948 g_object_notify (G_OBJECT (chat), "subject-contact");
949 g_object_notify (G_OBJECT (chat), "subject-timestamp");
953 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
960 g_object_notify (G_OBJECT (chat), "anonymous");
962 case PROP_INVITE_ONLY:
963 g_object_notify (G_OBJECT (chat), "invite-only");
966 g_object_notify (G_OBJECT (chat), "limit");
969 g_object_notify (G_OBJECT (chat), "limited");
972 g_object_notify (G_OBJECT (chat), "moderated");
975 g_object_notify (G_OBJECT (chat), "name");
977 case PROP_DESCRIPTION:
978 g_object_notify (G_OBJECT (chat), "description");
981 g_object_notify (G_OBJECT (chat), "password");
983 case PROP_PASSWORD_REQUIRED:
984 g_object_notify (G_OBJECT (chat), "password-required");
986 case PROP_PERSISTENT:
987 g_object_notify (G_OBJECT (chat), "persistent");
990 g_object_notify (G_OBJECT (chat), "private");
993 g_object_notify (G_OBJECT (chat), "subject");
995 case PROP_SUBJECT_CONTACT:
996 g_object_notify (G_OBJECT (chat), "subject-contact");
998 case PROP_SUBJECT_TIMESTAMP:
999 g_object_notify (G_OBJECT (chat), "subject-timestamp");