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);
349 if (priv->text_iface) {
350 dbus_g_proxy_disconnect_signal (priv->text_iface, "Received",
351 G_CALLBACK (tp_chat_received_cb),
353 dbus_g_proxy_disconnect_signal (priv->text_iface, "Sent",
354 G_CALLBACK (tp_chat_sent_cb),
356 dbus_g_proxy_disconnect_signal (priv->text_iface, "SendError",
357 G_CALLBACK (tp_chat_send_error_cb),
361 if (priv->chat_state_iface) {
362 dbus_g_proxy_disconnect_signal (priv->chat_state_iface, "ChatStateChanged",
363 G_CALLBACK (tp_chat_state_changed_cb),
368 g_signal_handlers_disconnect_by_func (priv->tp_chan,
371 dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
372 G_CALLBACK (tp_chat_closed_cb),
374 if (priv->acknowledge) {
375 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
376 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
377 empathy_debug (DEBUG_DOMAIN,
378 "Error closing text channel: %s",
379 error ? error->message : "No error given");
380 g_clear_error (&error);
383 g_object_unref (priv->tp_chan);
387 g_object_unref (priv->factory);
390 g_object_unref (priv->user);
393 g_object_unref (priv->account);
396 g_object_unref (priv->mc);
400 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
404 tp_chat_constructor (GType type,
406 GObjectConstructParam *props)
409 EmpathyTpChatPriv *priv;
411 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
413 priv = GET_PRIV (chat);
415 priv->factory = empathy_contact_factory_new ();
416 priv->user = empathy_contact_factory_get_user (priv->factory, priv->account);
417 priv->mc = empathy_mission_control_new ();
419 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
420 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
421 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
422 TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
423 priv->props_iface = tp_chan_get_interface (priv->tp_chan,
424 TELEPATHY_PROPS_IFACE_QUARK);
426 g_signal_connect (priv->tp_chan, "destroy",
427 G_CALLBACK (tp_chat_destroy_cb),
429 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
430 G_CALLBACK (tp_chat_closed_cb),
432 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
433 G_CALLBACK (tp_chat_received_cb),
435 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
436 G_CALLBACK (tp_chat_sent_cb),
438 dbus_g_proxy_connect_signal (priv->text_iface, "SendError",
439 G_CALLBACK (tp_chat_send_error_cb),
442 if (priv->chat_state_iface != NULL) {
443 dbus_g_proxy_connect_signal (priv->chat_state_iface,
445 G_CALLBACK (tp_chat_state_changed_cb),
448 if (priv->props_iface != NULL) {
449 tp_props_iface_set_mapping (TELEPATHY_PROPS_IFACE (priv->props_iface),
450 "anonymous", PROP_ANONYMOUS,
451 "invite-only", PROP_INVITE_ONLY,
453 "limited", PROP_LIMITED,
454 "moderated", PROP_MODERATED,
456 "description", PROP_DESCRIPTION,
457 "password", PROP_PASSWORD,
458 "password-required", PROP_PASSWORD_REQUIRED,
459 "persistent", PROP_PERSISTENT,
460 "private", PROP_PRIVATE,
461 "subject", PROP_SUBJECT,
462 "subject-contact", PROP_SUBJECT_CONTACT,
463 "subject-timestamp", PROP_SUBJECT_TIMESTAMP,
465 g_signal_connect (priv->props_iface, "properties-ready",
466 G_CALLBACK (tp_chat_properties_ready_cb),
468 g_signal_connect (priv->props_iface, "properties-changed",
469 G_CALLBACK (tp_chat_properties_changed_cb),
477 tp_chat_get_property (GObject *object,
482 EmpathyTpChatPriv *priv;
485 priv = GET_PRIV (object);
486 chat = EMPATHY_TP_CHAT (object);
488 if (param_id >= PROP_ANONYMOUS &&
489 param_id <= PROP_SUBJECT_TIMESTAMP) {
490 if (priv->props_iface) {
491 tp_props_iface_get_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
501 g_value_set_object (value, priv->account);
504 g_value_set_object (value, priv->tp_chan);
506 case PROP_ACKNOWLEDGE:
507 g_value_set_boolean (value, priv->acknowledge);
510 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
516 tp_chat_set_property (GObject *object,
521 EmpathyTpChatPriv *priv;
524 priv = GET_PRIV (object);
525 chat = EMPATHY_TP_CHAT (object);
527 if (param_id >= PROP_ANONYMOUS &&
528 param_id <= PROP_SUBJECT_TIMESTAMP) {
529 if (priv->props_iface) {
530 tp_props_iface_set_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
540 priv->account = g_object_ref (g_value_get_object (value));
543 priv->tp_chan = g_object_ref (g_value_get_object (value));
545 case PROP_ACKNOWLEDGE:
546 empathy_tp_chat_set_acknowledge (EMPATHY_TP_CHAT (object),
547 g_value_get_boolean (value));
550 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
556 empathy_tp_chat_new (McAccount *account,
559 return g_object_new (EMPATHY_TYPE_TP_CHAT,
566 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
573 const gchar *bus_name;
576 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
578 mc = empathy_mission_control_new ();
579 account = empathy_contact_get_account (contact);
581 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
582 /* The account is not connected, nothing to do. */
586 tp_conn = mission_control_get_connection (mc, account, NULL);
587 g_return_val_if_fail (tp_conn != NULL, NULL);
588 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
589 handle = empathy_contact_get_handle (contact);
591 text_chan = tp_conn_new_channel (tp_get_bus (),
594 TP_IFACE_CHANNEL_TYPE_TEXT,
595 TP_HANDLE_TYPE_CONTACT,
599 chat = empathy_tp_chat_new (account, text_chan);
601 g_object_unref (tp_conn);
602 g_object_unref (text_chan);
609 empathy_tp_chat_get_acknowledge (EmpathyTpChat *chat)
611 EmpathyTpChatPriv *priv;
613 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
615 priv = GET_PRIV (chat);
617 return priv->acknowledge;
621 empathy_tp_chat_set_acknowledge (EmpathyTpChat *chat,
622 gboolean acknowledge)
624 EmpathyTpChatPriv *priv;
626 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
628 priv = GET_PRIV (chat);
630 priv->acknowledge = acknowledge;
631 g_object_notify (G_OBJECT (chat), "acknowledge");
635 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
637 EmpathyTpChatPriv *priv;
639 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
641 priv = GET_PRIV (chat);
643 return priv->tp_chan;
647 empathy_tp_chat_get_pendings (EmpathyTpChat *chat)
649 EmpathyTpChatPriv *priv;
650 GPtrArray *messages_list;
652 GList *messages = NULL;
653 GError *error = NULL;
655 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
657 priv = GET_PRIV (chat);
659 /* If we do this call async, don't forget to ignore Received signal
660 * until we get the answer */
661 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
665 empathy_debug (DEBUG_DOMAIN,
666 "Error retrieving pending messages: %s",
667 error ? error->message : "No error given");
668 g_clear_error (&error);
672 for (i = 0; i < messages_list->len; i++) {
673 EmpathyMessage *message;
674 GValueArray *message_struct;
675 const gchar *message_body;
682 message_struct = g_ptr_array_index (messages_list, i);
684 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
685 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
686 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
687 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
688 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
689 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
691 empathy_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
693 message = tp_chat_build_message (chat,
699 messages = g_list_prepend (messages, message);
701 g_value_array_free (message_struct);
703 messages = g_list_reverse (messages);
705 g_ptr_array_free (messages_list, TRUE);
711 empathy_tp_chat_send (EmpathyTpChat *chat,
712 EmpathyMessage *message)
714 EmpathyTpChatPriv *priv;
715 const gchar *message_body;
716 EmpathyMessageType message_type;
717 GError *error = NULL;
719 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
720 g_return_if_fail (EMPATHY_IS_MESSAGE (message));
722 priv = GET_PRIV (chat);
724 message_body = empathy_message_get_body (message);
725 message_type = empathy_message_get_type (message);
727 empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
728 if (!tp_chan_type_text_send (priv->text_iface,
732 empathy_debug (DEBUG_DOMAIN,
734 error ? error->message : "No error given");
735 g_clear_error (&error);
740 empathy_tp_chat_set_state (EmpathyTpChat *chat,
741 TelepathyChannelChatState state)
743 EmpathyTpChatPriv *priv;
744 GError *error = NULL;
746 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
748 priv = GET_PRIV (chat);
750 if (priv->chat_state_iface) {
751 empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
752 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
755 empathy_debug (DEBUG_DOMAIN,
756 "Set Chat State Error: %s",
757 error ? error->message : "No error given");
758 g_clear_error (&error);
764 empathy_tp_chat_get_id (EmpathyTpChat *chat)
766 EmpathyTpChatPriv *priv;
768 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
770 priv = GET_PRIV (chat);
773 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
780 tp_chat_destroy_cb (TpChan *text_chan,
783 EmpathyTpChatPriv *priv;
785 priv = GET_PRIV (chat);
787 empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
789 g_object_unref (priv->tp_chan);
790 priv->tp_chan = NULL;
791 priv->text_iface = NULL;
792 priv->chat_state_iface = NULL;
793 priv->props_iface = NULL;
795 g_signal_emit (chat, signals[DESTROY], 0);
799 tp_chat_closed_cb (TpChan *text_chan,
802 EmpathyTpChatPriv *priv;
804 priv = GET_PRIV (chat);
806 /* The channel is closed, do just like if the proxy was destroyed */
807 g_signal_handlers_disconnect_by_func (priv->tp_chan,
810 tp_chat_destroy_cb (text_chan, chat);
814 tp_chat_received_cb (DBusGProxy *text_iface,
823 EmpathyTpChatPriv *priv;
824 EmpathyMessage *message;
826 priv = GET_PRIV (chat);
828 empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
830 message = tp_chat_build_message (chat,
836 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
837 g_object_unref (message);
839 if (priv->acknowledge) {
842 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
843 g_array_append_val (message_ids, message_id);
844 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
846 g_array_free (message_ids, TRUE);
851 tp_chat_sent_cb (DBusGProxy *text_iface,
857 EmpathyMessage *message;
859 empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
861 message = tp_chat_build_message (chat,
867 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
868 g_object_unref (message);
872 tp_chat_send_error_cb (DBusGProxy *text_iface,
879 EmpathyMessage *message;
881 empathy_debug (DEBUG_DOMAIN, "Message sent error: %s (%d)",
882 message_body, error_code);
884 message = tp_chat_build_message (chat,
890 g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
891 g_object_unref (message);
895 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
897 TelepathyChannelChatState state,
900 EmpathyTpChatPriv *priv;
901 EmpathyContact *contact;
903 priv = GET_PRIV (chat);
905 contact = empathy_contact_factory_get_from_handle (priv->factory,
909 empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
910 empathy_contact_get_name (contact),
914 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
915 g_object_unref (contact);
918 static EmpathyMessage *
919 tp_chat_build_message (EmpathyTpChat *chat,
923 const gchar *message_body)
925 EmpathyTpChatPriv *priv;
926 EmpathyMessage *message;
927 EmpathyContact *sender;
929 priv = GET_PRIV (chat);
931 if (from_handle == 0) {
932 sender = g_object_ref (priv->user);
934 sender = empathy_contact_factory_get_from_handle (priv->factory,
939 message = empathy_message_new (message_body);
940 empathy_message_set_type (message, type);
941 empathy_message_set_sender (message, sender);
942 empathy_message_set_receiver (message, priv->user);
943 empathy_message_set_timestamp (message, timestamp);
945 g_object_unref (sender);
951 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
954 g_object_notify (G_OBJECT (chat), "anonymous");
955 g_object_notify (G_OBJECT (chat), "invite-only");
956 g_object_notify (G_OBJECT (chat), "limit");
957 g_object_notify (G_OBJECT (chat), "limited");
958 g_object_notify (G_OBJECT (chat), "moderated");
959 g_object_notify (G_OBJECT (chat), "name");
960 g_object_notify (G_OBJECT (chat), "description");
961 g_object_notify (G_OBJECT (chat), "password");
962 g_object_notify (G_OBJECT (chat), "password-required");
963 g_object_notify (G_OBJECT (chat), "persistent");
964 g_object_notify (G_OBJECT (chat), "private");
965 g_object_notify (G_OBJECT (chat), "subject");
966 g_object_notify (G_OBJECT (chat), "subject-contact");
967 g_object_notify (G_OBJECT (chat), "subject-timestamp");
971 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
978 g_object_notify (G_OBJECT (chat), "anonymous");
980 case PROP_INVITE_ONLY:
981 g_object_notify (G_OBJECT (chat), "invite-only");
984 g_object_notify (G_OBJECT (chat), "limit");
987 g_object_notify (G_OBJECT (chat), "limited");
990 g_object_notify (G_OBJECT (chat), "moderated");
993 g_object_notify (G_OBJECT (chat), "name");
995 case PROP_DESCRIPTION:
996 g_object_notify (G_OBJECT (chat), "description");
999 g_object_notify (G_OBJECT (chat), "password");
1001 case PROP_PASSWORD_REQUIRED:
1002 g_object_notify (G_OBJECT (chat), "password-required");
1004 case PROP_PERSISTENT:
1005 g_object_notify (G_OBJECT (chat), "persistent");
1008 g_object_notify (G_OBJECT (chat), "private");
1011 g_object_notify (G_OBJECT (chat), "subject");
1013 case PROP_SUBJECT_CONTACT:
1014 g_object_notify (G_OBJECT (chat), "subject-contact");
1016 case PROP_SUBJECT_TIMESTAMP:
1017 g_object_notify (G_OBJECT (chat), "subject-timestamp");