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-factory.h"
35 #include "empathy-marshal.h"
36 #include "empathy-debug.h"
37 #include "empathy-time.h"
38 #include "empathy-utils.h"
40 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
41 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv))
43 #define DEBUG_DOMAIN "TpChat"
45 struct _EmpathyTpChatPriv {
46 EmpathyContactFactory *factory;
54 DBusGProxy *props_iface;
55 DBusGProxy *text_iface;
56 DBusGProxy *chat_state_iface;
59 static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass);
60 static void empathy_tp_chat_init (EmpathyTpChat *chat);
61 static void tp_chat_finalize (GObject *object);
62 static GObject * tp_chat_constructor (GType type,
64 GObjectConstructParam *props);
65 static void tp_chat_get_property (GObject *object,
69 static void tp_chat_set_property (GObject *object,
73 static void tp_chat_destroy_cb (TpChan *text_chan,
75 static void tp_chat_closed_cb (TpChan *text_chan,
77 static void tp_chat_received_cb (DBusGProxy *text_iface,
85 static void tp_chat_sent_cb (DBusGProxy *text_iface,
90 static void tp_chat_send_error_cb (DBusGProxy *text_iface,
96 static void tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
98 TelepathyChannelChatState state,
100 static EmpathyMessage * tp_chat_build_message (EmpathyTpChat *chat,
104 const gchar *message_body);
105 static void tp_chat_properties_ready_cb (TpPropsIface *props_iface,
106 EmpathyTpChat *chat);
107 static void tp_chat_properties_changed_cb (TpPropsIface *props_iface,
110 EmpathyTpChat *chat);
125 PROP_PASSWORD_REQUIRED,
129 PROP_SUBJECT_CONTACT,
130 PROP_SUBJECT_TIMESTAMP
141 static guint signals[LAST_SIGNAL];
143 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
146 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
148 GObjectClass *object_class = G_OBJECT_CLASS (klass);
150 object_class->finalize = tp_chat_finalize;
151 object_class->constructor = tp_chat_constructor;
152 object_class->get_property = tp_chat_get_property;
153 object_class->set_property = tp_chat_set_property;
155 /* Construct-only properties */
156 g_object_class_install_property (object_class,
158 g_param_spec_object ("account",
160 "The account associated with the channel",
163 G_PARAM_CONSTRUCT_ONLY));
164 g_object_class_install_property (object_class,
166 g_param_spec_object ("tp-chan",
168 "The text channel for the chat",
171 G_PARAM_CONSTRUCT_ONLY));
173 /* Normal properties */
174 g_object_class_install_property (object_class,
176 g_param_spec_boolean ("acknowledge",
182 /* Properties of Text Channel */
183 g_object_class_install_property (object_class,
185 g_param_spec_boolean ("anonymous",
190 g_object_class_install_property (object_class,
192 g_param_spec_boolean ("invite-only",
197 g_object_class_install_property (object_class,
199 g_param_spec_uint ("limit",
206 g_object_class_install_property (object_class,
208 g_param_spec_boolean ("limited",
213 g_object_class_install_property (object_class,
215 g_param_spec_boolean ("moderated",
220 g_object_class_install_property (object_class,
222 g_param_spec_string ("name",
227 g_object_class_install_property (object_class,
229 g_param_spec_string ("description",
234 g_object_class_install_property (object_class,
236 g_param_spec_string ("password",
241 g_object_class_install_property (object_class,
242 PROP_PASSWORD_REQUIRED,
243 g_param_spec_boolean ("password-required",
248 g_object_class_install_property (object_class,
250 g_param_spec_boolean ("persistent",
255 g_object_class_install_property (object_class,
257 g_param_spec_boolean ("private",
263 g_object_class_install_property (object_class,
265 g_param_spec_string ("subject",
270 g_object_class_install_property (object_class,
271 PROP_SUBJECT_CONTACT,
272 g_param_spec_uint ("subject-contact",
279 g_object_class_install_property (object_class,
280 PROP_SUBJECT_TIMESTAMP,
281 g_param_spec_uint ("subject-timestamp",
290 signals[MESSAGE_RECEIVED] =
291 g_signal_new ("message-received",
292 G_TYPE_FROM_CLASS (klass),
296 g_cclosure_marshal_VOID__OBJECT,
298 1, EMPATHY_TYPE_MESSAGE);
300 signals[SEND_ERROR] =
301 g_signal_new ("send-error",
302 G_TYPE_FROM_CLASS (klass),
306 empathy_marshal_VOID__OBJECT_UINT,
308 2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
310 signals[CHAT_STATE_CHANGED] =
311 g_signal_new ("chat-state-changed",
312 G_TYPE_FROM_CLASS (klass),
316 empathy_marshal_VOID__OBJECT_UINT,
318 2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
321 g_signal_new ("destroy",
322 G_TYPE_FROM_CLASS (klass),
326 g_cclosure_marshal_VOID__VOID,
330 g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
334 empathy_tp_chat_init (EmpathyTpChat *chat)
340 tp_chat_finalize (GObject *object)
342 EmpathyTpChatPriv *priv;
344 GError *error = NULL;
346 chat = EMPATHY_TP_CHAT (object);
347 priv = GET_PRIV (chat);
350 g_signal_handlers_disconnect_by_func (priv->tp_chan,
353 if (priv->acknowledge) {
354 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
355 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
356 empathy_debug (DEBUG_DOMAIN,
357 "Error closing text channel: %s",
358 error ? error->message : "No error given");
359 g_clear_error (&error);
362 g_object_unref (priv->tp_chan);
366 g_object_unref (priv->factory);
369 g_object_unref (priv->user);
372 g_object_unref (priv->account);
375 g_object_unref (priv->mc);
379 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
383 tp_chat_constructor (GType type,
385 GObjectConstructParam *props)
388 EmpathyTpChatPriv *priv;
390 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
392 priv = GET_PRIV (chat);
394 priv->factory = empathy_contact_factory_new ();
395 priv->user = empathy_contact_factory_get_user (priv->factory, priv->account);
396 priv->mc = empathy_mission_control_new ();
398 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
399 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
400 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
401 TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
402 priv->props_iface = tp_chan_get_interface (priv->tp_chan,
403 TELEPATHY_PROPS_IFACE_QUARK);
405 g_signal_connect (priv->tp_chan, "destroy",
406 G_CALLBACK (tp_chat_destroy_cb),
408 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
409 G_CALLBACK (tp_chat_closed_cb),
411 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
412 G_CALLBACK (tp_chat_received_cb),
414 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
415 G_CALLBACK (tp_chat_sent_cb),
417 dbus_g_proxy_connect_signal (priv->text_iface, "SendError",
418 G_CALLBACK (tp_chat_send_error_cb),
421 if (priv->chat_state_iface != NULL) {
422 dbus_g_proxy_connect_signal (priv->chat_state_iface,
424 G_CALLBACK (tp_chat_state_changed_cb),
427 if (priv->props_iface != NULL) {
428 tp_props_iface_set_mapping (TELEPATHY_PROPS_IFACE (priv->props_iface),
429 "anonymous", PROP_ANONYMOUS,
430 "invite-only", PROP_INVITE_ONLY,
432 "limited", PROP_LIMITED,
433 "moderated", PROP_MODERATED,
435 "description", PROP_DESCRIPTION,
436 "password", PROP_PASSWORD,
437 "password-required", PROP_PASSWORD_REQUIRED,
438 "persistent", PROP_PERSISTENT,
439 "private", PROP_PRIVATE,
440 "subject", PROP_SUBJECT,
441 "subject-contact", PROP_SUBJECT_CONTACT,
442 "subject-timestamp", PROP_SUBJECT_TIMESTAMP,
444 g_signal_connect (priv->props_iface, "properties-ready",
445 G_CALLBACK (tp_chat_properties_ready_cb),
447 g_signal_connect (priv->props_iface, "properties-changed",
448 G_CALLBACK (tp_chat_properties_changed_cb),
456 tp_chat_get_property (GObject *object,
461 EmpathyTpChatPriv *priv;
464 priv = GET_PRIV (object);
465 chat = EMPATHY_TP_CHAT (object);
467 if (param_id >= PROP_ANONYMOUS &&
468 param_id <= PROP_SUBJECT_TIMESTAMP) {
469 if (priv->props_iface) {
470 tp_props_iface_get_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
480 g_value_set_object (value, priv->account);
483 g_value_set_object (value, priv->tp_chan);
485 case PROP_ACKNOWLEDGE:
486 g_value_set_boolean (value, priv->acknowledge);
489 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
495 tp_chat_set_property (GObject *object,
500 EmpathyTpChatPriv *priv;
503 priv = GET_PRIV (object);
504 chat = EMPATHY_TP_CHAT (object);
506 if (param_id >= PROP_ANONYMOUS &&
507 param_id <= PROP_SUBJECT_TIMESTAMP) {
508 if (priv->props_iface) {
509 tp_props_iface_set_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
519 priv->account = g_object_ref (g_value_get_object (value));
522 priv->tp_chan = g_object_ref (g_value_get_object (value));
524 case PROP_ACKNOWLEDGE:
525 empathy_tp_chat_set_acknowledge (EMPATHY_TP_CHAT (object),
526 g_value_get_boolean (value));
529 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
535 empathy_tp_chat_new (McAccount *account,
538 return g_object_new (EMPATHY_TYPE_TP_CHAT,
545 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
552 const gchar *bus_name;
555 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
557 mc = empathy_mission_control_new ();
558 account = empathy_contact_get_account (contact);
560 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
561 /* The account is not connected, nothing to do. */
565 tp_conn = mission_control_get_connection (mc, account, NULL);
566 g_return_val_if_fail (tp_conn != NULL, NULL);
567 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
568 handle = empathy_contact_get_handle (contact);
570 text_chan = tp_conn_new_channel (tp_get_bus (),
573 TP_IFACE_CHANNEL_TYPE_TEXT,
574 TP_HANDLE_TYPE_CONTACT,
578 chat = empathy_tp_chat_new (account, text_chan);
580 g_object_unref (tp_conn);
581 g_object_unref (text_chan);
588 empathy_tp_chat_get_acknowledge (EmpathyTpChat *chat)
590 EmpathyTpChatPriv *priv;
592 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
594 priv = GET_PRIV (chat);
596 return priv->acknowledge;
600 empathy_tp_chat_set_acknowledge (EmpathyTpChat *chat,
601 gboolean acknowledge)
603 EmpathyTpChatPriv *priv;
605 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
607 priv = GET_PRIV (chat);
609 priv->acknowledge = acknowledge;
610 g_object_notify (G_OBJECT (chat), "acknowledge");
614 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
616 EmpathyTpChatPriv *priv;
618 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
620 priv = GET_PRIV (chat);
622 return priv->tp_chan;
626 empathy_tp_chat_get_pendings (EmpathyTpChat *chat)
628 EmpathyTpChatPriv *priv;
629 GPtrArray *messages_list;
631 GList *messages = NULL;
632 GError *error = NULL;
634 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
636 priv = GET_PRIV (chat);
638 /* If we do this call async, don't forget to ignore Received signal
639 * until we get the answer */
640 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
644 empathy_debug (DEBUG_DOMAIN,
645 "Error retrieving pending messages: %s",
646 error ? error->message : "No error given");
647 g_clear_error (&error);
651 for (i = 0; i < messages_list->len; i++) {
652 EmpathyMessage *message;
653 GValueArray *message_struct;
654 const gchar *message_body;
661 message_struct = g_ptr_array_index (messages_list, i);
663 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
664 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
665 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
666 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
667 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
668 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
670 empathy_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
672 message = tp_chat_build_message (chat,
678 messages = g_list_prepend (messages, message);
680 g_value_array_free (message_struct);
682 messages = g_list_reverse (messages);
684 g_ptr_array_free (messages_list, TRUE);
690 empathy_tp_chat_send (EmpathyTpChat *chat,
691 EmpathyMessage *message)
693 EmpathyTpChatPriv *priv;
694 const gchar *message_body;
695 EmpathyMessageType message_type;
696 GError *error = NULL;
698 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
699 g_return_if_fail (EMPATHY_IS_MESSAGE (message));
701 priv = GET_PRIV (chat);
703 message_body = empathy_message_get_body (message);
704 message_type = empathy_message_get_type (message);
706 empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
707 if (!tp_chan_type_text_send (priv->text_iface,
711 empathy_debug (DEBUG_DOMAIN,
713 error ? error->message : "No error given");
714 g_clear_error (&error);
719 empathy_tp_chat_set_state (EmpathyTpChat *chat,
720 TelepathyChannelChatState state)
722 EmpathyTpChatPriv *priv;
723 GError *error = NULL;
725 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
727 priv = GET_PRIV (chat);
729 if (priv->chat_state_iface) {
730 empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
731 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
734 empathy_debug (DEBUG_DOMAIN,
735 "Set Chat State Error: %s",
736 error ? error->message : "No error given");
737 g_clear_error (&error);
743 empathy_tp_chat_get_id (EmpathyTpChat *chat)
745 EmpathyTpChatPriv *priv;
747 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
749 priv = GET_PRIV (chat);
752 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
759 tp_chat_destroy_cb (TpChan *text_chan,
762 EmpathyTpChatPriv *priv;
764 priv = GET_PRIV (chat);
766 empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
768 g_object_unref (priv->tp_chan);
769 priv->tp_chan = NULL;
770 priv->text_iface = NULL;
771 priv->chat_state_iface = NULL;
772 priv->props_iface = NULL;
774 g_signal_emit (chat, signals[DESTROY], 0);
778 tp_chat_closed_cb (TpChan *text_chan,
781 EmpathyTpChatPriv *priv;
783 priv = GET_PRIV (chat);
785 /* The channel is closed, do just like if the proxy was destroyed */
786 g_signal_handlers_disconnect_by_func (priv->tp_chan,
789 tp_chat_destroy_cb (text_chan, chat);
793 tp_chat_received_cb (DBusGProxy *text_iface,
802 EmpathyTpChatPriv *priv;
803 EmpathyMessage *message;
805 priv = GET_PRIV (chat);
807 empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
809 message = tp_chat_build_message (chat,
815 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
816 g_object_unref (message);
818 if (priv->acknowledge) {
821 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
822 g_array_append_val (message_ids, message_id);
823 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
825 g_array_free (message_ids, TRUE);
830 tp_chat_sent_cb (DBusGProxy *text_iface,
836 EmpathyMessage *message;
838 empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
840 message = tp_chat_build_message (chat,
846 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
847 g_object_unref (message);
851 tp_chat_send_error_cb (DBusGProxy *text_iface,
858 EmpathyMessage *message;
860 empathy_debug (DEBUG_DOMAIN, "Message sent error: %s (%d)",
861 message_body, error_code);
863 message = tp_chat_build_message (chat,
869 g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
870 g_object_unref (message);
874 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
876 TelepathyChannelChatState state,
879 EmpathyTpChatPriv *priv;
880 EmpathyContact *contact;
882 priv = GET_PRIV (chat);
884 contact = empathy_contact_factory_get_from_handle (priv->factory,
888 empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
889 empathy_contact_get_name (contact),
893 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
894 g_object_unref (contact);
897 static EmpathyMessage *
898 tp_chat_build_message (EmpathyTpChat *chat,
902 const gchar *message_body)
904 EmpathyTpChatPriv *priv;
905 EmpathyMessage *message;
906 EmpathyContact *sender;
908 priv = GET_PRIV (chat);
910 if (from_handle == 0) {
911 sender = g_object_ref (priv->user);
913 sender = empathy_contact_factory_get_from_handle (priv->factory,
918 message = empathy_message_new (message_body);
919 empathy_message_set_type (message, type);
920 empathy_message_set_sender (message, sender);
921 empathy_message_set_receiver (message, priv->user);
922 empathy_message_set_timestamp (message, (EmpathyTime) timestamp);
924 g_object_unref (sender);
930 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
933 g_object_notify (G_OBJECT (chat), "anonymous");
934 g_object_notify (G_OBJECT (chat), "invite-only");
935 g_object_notify (G_OBJECT (chat), "limit");
936 g_object_notify (G_OBJECT (chat), "limited");
937 g_object_notify (G_OBJECT (chat), "moderated");
938 g_object_notify (G_OBJECT (chat), "name");
939 g_object_notify (G_OBJECT (chat), "description");
940 g_object_notify (G_OBJECT (chat), "password");
941 g_object_notify (G_OBJECT (chat), "password-required");
942 g_object_notify (G_OBJECT (chat), "persistent");
943 g_object_notify (G_OBJECT (chat), "private");
944 g_object_notify (G_OBJECT (chat), "subject");
945 g_object_notify (G_OBJECT (chat), "subject-contact");
946 g_object_notify (G_OBJECT (chat), "subject-timestamp");
950 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
957 g_object_notify (G_OBJECT (chat), "anonymous");
959 case PROP_INVITE_ONLY:
960 g_object_notify (G_OBJECT (chat), "invite-only");
963 g_object_notify (G_OBJECT (chat), "limit");
966 g_object_notify (G_OBJECT (chat), "limited");
969 g_object_notify (G_OBJECT (chat), "moderated");
972 g_object_notify (G_OBJECT (chat), "name");
974 case PROP_DESCRIPTION:
975 g_object_notify (G_OBJECT (chat), "description");
978 g_object_notify (G_OBJECT (chat), "password");
980 case PROP_PASSWORD_REQUIRED:
981 g_object_notify (G_OBJECT (chat), "password-required");
983 case PROP_PERSISTENT:
984 g_object_notify (G_OBJECT (chat), "persistent");
987 g_object_notify (G_OBJECT (chat), "private");
990 g_object_notify (G_OBJECT (chat), "subject");
992 case PROP_SUBJECT_CONTACT:
993 g_object_notify (G_OBJECT (chat), "subject-contact");
995 case PROP_SUBJECT_TIMESTAMP:
996 g_object_notify (G_OBJECT (chat), "subject-timestamp");