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);
685 g_ptr_array_free (messages_list, TRUE);
691 empathy_tp_chat_send (EmpathyTpChat *chat,
692 EmpathyMessage *message)
694 EmpathyTpChatPriv *priv;
695 const gchar *message_body;
696 EmpathyMessageType message_type;
697 GError *error = NULL;
699 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
700 g_return_if_fail (EMPATHY_IS_MESSAGE (message));
702 priv = GET_PRIV (chat);
704 message_body = empathy_message_get_body (message);
705 message_type = empathy_message_get_type (message);
707 empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
708 if (!tp_chan_type_text_send (priv->text_iface,
712 empathy_debug (DEBUG_DOMAIN,
714 error ? error->message : "No error given");
715 g_clear_error (&error);
720 empathy_tp_chat_set_state (EmpathyTpChat *chat,
721 TelepathyChannelChatState state)
723 EmpathyTpChatPriv *priv;
724 GError *error = NULL;
726 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
728 priv = GET_PRIV (chat);
730 if (priv->chat_state_iface) {
731 empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
732 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
735 empathy_debug (DEBUG_DOMAIN,
736 "Set Chat State Error: %s",
737 error ? error->message : "No error given");
738 g_clear_error (&error);
744 empathy_tp_chat_get_id (EmpathyTpChat *chat)
746 EmpathyTpChatPriv *priv;
748 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
750 priv = GET_PRIV (chat);
756 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
762 tp_chat_destroy_cb (TpChan *text_chan,
765 EmpathyTpChatPriv *priv;
767 priv = GET_PRIV (chat);
769 empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
771 g_object_unref (priv->tp_chan);
772 priv->tp_chan = NULL;
773 priv->text_iface = NULL;
774 priv->chat_state_iface = NULL;
775 priv->props_iface = NULL;
777 g_signal_emit (chat, signals[DESTROY], 0);
781 tp_chat_closed_cb (TpChan *text_chan,
784 EmpathyTpChatPriv *priv;
786 priv = GET_PRIV (chat);
788 /* The channel is closed, do just like if the proxy was destroyed */
789 g_signal_handlers_disconnect_by_func (priv->tp_chan,
792 tp_chat_destroy_cb (text_chan, chat);
796 tp_chat_received_cb (DBusGProxy *text_iface,
805 EmpathyTpChatPriv *priv;
806 EmpathyMessage *message;
808 priv = GET_PRIV (chat);
810 empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
812 message = tp_chat_build_message (chat,
818 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
819 g_object_unref (message);
821 if (priv->acknowledge) {
824 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
825 g_array_append_val (message_ids, message_id);
826 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
828 g_array_free (message_ids, TRUE);
833 tp_chat_sent_cb (DBusGProxy *text_iface,
839 EmpathyMessage *message;
841 empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
843 message = tp_chat_build_message (chat,
849 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
850 g_object_unref (message);
854 tp_chat_send_error_cb (DBusGProxy *text_iface,
861 EmpathyMessage *message;
863 empathy_debug (DEBUG_DOMAIN, "Message sent error: %s (%d)",
864 message_body, error_code);
866 message = tp_chat_build_message (chat,
872 g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
873 g_object_unref (message);
877 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
879 TelepathyChannelChatState state,
882 EmpathyTpChatPriv *priv;
883 EmpathyContact *contact;
885 priv = GET_PRIV (chat);
887 contact = empathy_tp_contact_list_get_from_handle (priv->list, handle);
889 empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
890 empathy_contact_get_name (contact),
894 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
895 g_object_unref (contact);
898 static EmpathyMessage *
899 tp_chat_build_message (EmpathyTpChat *chat,
903 const gchar *message_body)
905 EmpathyTpChatPriv *priv;
906 EmpathyMessage *message;
907 EmpathyContact *sender;
908 EmpathyContact *receiver;
910 priv = GET_PRIV (chat);
912 receiver = empathy_tp_contact_list_get_user (priv->list);
913 if (from_handle == 0) {
914 sender = g_object_ref (receiver);
916 sender = empathy_tp_contact_list_get_from_handle (priv->list,
920 message = empathy_message_new (message_body);
921 empathy_message_set_type (message, type);
922 empathy_message_set_sender (message, sender);
923 empathy_message_set_receiver (message, receiver);
924 empathy_message_set_timestamp (message, (EmpathyTime) timestamp);
926 g_object_unref (sender);
932 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
935 g_object_notify (G_OBJECT (chat), "anonymous");
936 g_object_notify (G_OBJECT (chat), "invite-only");
937 g_object_notify (G_OBJECT (chat), "limit");
938 g_object_notify (G_OBJECT (chat), "limited");
939 g_object_notify (G_OBJECT (chat), "moderated");
940 g_object_notify (G_OBJECT (chat), "name");
941 g_object_notify (G_OBJECT (chat), "description");
942 g_object_notify (G_OBJECT (chat), "password");
943 g_object_notify (G_OBJECT (chat), "password-required");
944 g_object_notify (G_OBJECT (chat), "persistent");
945 g_object_notify (G_OBJECT (chat), "private");
946 g_object_notify (G_OBJECT (chat), "subject");
947 g_object_notify (G_OBJECT (chat), "subject-contact");
948 g_object_notify (G_OBJECT (chat), "subject-timestamp");
952 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
959 g_object_notify (G_OBJECT (chat), "anonymous");
961 case PROP_INVITE_ONLY:
962 g_object_notify (G_OBJECT (chat), "invite-only");
965 g_object_notify (G_OBJECT (chat), "limit");
968 g_object_notify (G_OBJECT (chat), "limited");
971 g_object_notify (G_OBJECT (chat), "moderated");
974 g_object_notify (G_OBJECT (chat), "name");
976 case PROP_DESCRIPTION:
977 g_object_notify (G_OBJECT (chat), "description");
980 g_object_notify (G_OBJECT (chat), "password");
982 case PROP_PASSWORD_REQUIRED:
983 g_object_notify (G_OBJECT (chat), "password-required");
985 case PROP_PERSISTENT:
986 g_object_notify (G_OBJECT (chat), "persistent");
989 g_object_notify (G_OBJECT (chat), "private");
992 g_object_notify (G_OBJECT (chat), "subject");
994 case PROP_SUBJECT_CONTACT:
995 g_object_notify (G_OBJECT (chat), "subject-contact");
997 case PROP_SUBJECT_TIMESTAMP:
998 g_object_notify (G_OBJECT (chat), "subject-timestamp");