1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007 Collabora Ltd.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library 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 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * Authors: Xavier Claessens <xclaesse@gmail.com>
26 #include <libtelepathy/tp-chan-type-text-gen.h>
27 #include <libtelepathy/tp-chan-iface-chat-state-gen.h>
28 #include <libtelepathy/tp-conn.h>
29 #include <libtelepathy/tp-helpers.h>
30 #include <libtelepathy/tp-props-iface.h>
32 #include "empathy-tp-chat.h"
33 #include "empathy-contact-factory.h"
34 #include "empathy-marshal.h"
35 #include "empathy-debug.h"
36 #include "empathy-time.h"
37 #include "empathy-utils.h"
39 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
40 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv))
42 #define DEBUG_DOMAIN "TpChat"
44 struct _EmpathyTpChatPriv {
45 EmpathyContactFactory *factory;
53 DBusGProxy *props_iface;
54 DBusGProxy *text_iface;
55 DBusGProxy *chat_state_iface;
58 static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass);
59 static void empathy_tp_chat_init (EmpathyTpChat *chat);
60 static void tp_chat_finalize (GObject *object);
61 static GObject * tp_chat_constructor (GType type,
63 GObjectConstructParam *props);
64 static void tp_chat_get_property (GObject *object,
68 static void tp_chat_set_property (GObject *object,
72 static void tp_chat_destroy_cb (TpChan *text_chan,
74 static void tp_chat_closed_cb (TpChan *text_chan,
76 static void tp_chat_received_cb (DBusGProxy *text_iface,
84 static void tp_chat_sent_cb (DBusGProxy *text_iface,
89 static void tp_chat_send_error_cb (DBusGProxy *text_iface,
95 static void tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
97 TpChannelChatState state,
99 static EmpathyMessage * tp_chat_build_message (EmpathyTpChat *chat,
103 const gchar *message_body);
104 static void tp_chat_properties_ready_cb (TpPropsIface *props_iface,
105 EmpathyTpChat *chat);
106 static void tp_chat_properties_changed_cb (TpPropsIface *props_iface,
109 EmpathyTpChat *chat);
124 PROP_PASSWORD_REQUIRED,
128 PROP_SUBJECT_CONTACT,
129 PROP_SUBJECT_TIMESTAMP
140 static guint signals[LAST_SIGNAL];
142 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
145 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
147 GObjectClass *object_class = G_OBJECT_CLASS (klass);
149 object_class->finalize = tp_chat_finalize;
150 object_class->constructor = tp_chat_constructor;
151 object_class->get_property = tp_chat_get_property;
152 object_class->set_property = tp_chat_set_property;
154 /* Construct-only properties */
155 g_object_class_install_property (object_class,
157 g_param_spec_object ("account",
159 "The account associated with the channel",
162 G_PARAM_CONSTRUCT_ONLY));
163 g_object_class_install_property (object_class,
165 g_param_spec_object ("tp-chan",
167 "The text channel for the chat",
170 G_PARAM_CONSTRUCT_ONLY));
172 /* Normal properties */
173 g_object_class_install_property (object_class,
175 g_param_spec_boolean ("acknowledge",
181 /* Properties of Text Channel */
182 g_object_class_install_property (object_class,
184 g_param_spec_boolean ("anonymous",
189 g_object_class_install_property (object_class,
191 g_param_spec_boolean ("invite-only",
196 g_object_class_install_property (object_class,
198 g_param_spec_uint ("limit",
205 g_object_class_install_property (object_class,
207 g_param_spec_boolean ("limited",
212 g_object_class_install_property (object_class,
214 g_param_spec_boolean ("moderated",
219 g_object_class_install_property (object_class,
221 g_param_spec_string ("name",
226 g_object_class_install_property (object_class,
228 g_param_spec_string ("description",
233 g_object_class_install_property (object_class,
235 g_param_spec_string ("password",
240 g_object_class_install_property (object_class,
241 PROP_PASSWORD_REQUIRED,
242 g_param_spec_boolean ("password-required",
247 g_object_class_install_property (object_class,
249 g_param_spec_boolean ("persistent",
254 g_object_class_install_property (object_class,
256 g_param_spec_boolean ("private",
262 g_object_class_install_property (object_class,
264 g_param_spec_string ("subject",
269 g_object_class_install_property (object_class,
270 PROP_SUBJECT_CONTACT,
271 g_param_spec_uint ("subject-contact",
278 g_object_class_install_property (object_class,
279 PROP_SUBJECT_TIMESTAMP,
280 g_param_spec_uint ("subject-timestamp",
289 signals[MESSAGE_RECEIVED] =
290 g_signal_new ("message-received",
291 G_TYPE_FROM_CLASS (klass),
295 g_cclosure_marshal_VOID__OBJECT,
297 1, EMPATHY_TYPE_MESSAGE);
299 signals[SEND_ERROR] =
300 g_signal_new ("send-error",
301 G_TYPE_FROM_CLASS (klass),
305 _empathy_marshal_VOID__OBJECT_UINT,
307 2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
309 signals[CHAT_STATE_CHANGED] =
310 g_signal_new ("chat-state-changed",
311 G_TYPE_FROM_CLASS (klass),
315 _empathy_marshal_VOID__OBJECT_UINT,
317 2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
320 g_signal_new ("destroy",
321 G_TYPE_FROM_CLASS (klass),
325 g_cclosure_marshal_VOID__VOID,
329 g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
333 empathy_tp_chat_init (EmpathyTpChat *chat)
339 tp_chat_finalize (GObject *object)
341 EmpathyTpChatPriv *priv;
343 GError *error = NULL;
345 chat = EMPATHY_TP_CHAT (object);
346 priv = GET_PRIV (chat);
348 if (priv->text_iface) {
349 dbus_g_proxy_disconnect_signal (priv->text_iface, "Received",
350 G_CALLBACK (tp_chat_received_cb),
352 dbus_g_proxy_disconnect_signal (priv->text_iface, "Sent",
353 G_CALLBACK (tp_chat_sent_cb),
355 dbus_g_proxy_disconnect_signal (priv->text_iface, "SendError",
356 G_CALLBACK (tp_chat_send_error_cb),
360 if (priv->chat_state_iface) {
361 dbus_g_proxy_disconnect_signal (priv->chat_state_iface, "ChatStateChanged",
362 G_CALLBACK (tp_chat_state_changed_cb),
367 g_signal_handlers_disconnect_by_func (priv->tp_chan,
370 dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
371 G_CALLBACK (tp_chat_closed_cb),
373 if (priv->acknowledge) {
374 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
375 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
376 empathy_debug (DEBUG_DOMAIN,
377 "Error closing text channel: %s",
378 error ? error->message : "No error given");
379 g_clear_error (&error);
382 g_object_unref (priv->tp_chan);
386 g_object_unref (priv->factory);
389 g_object_unref (priv->user);
392 g_object_unref (priv->account);
395 g_object_unref (priv->mc);
399 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
403 tp_chat_constructor (GType type,
405 GObjectConstructParam *props)
408 EmpathyTpChatPriv *priv;
410 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
412 priv = GET_PRIV (chat);
414 priv->factory = empathy_contact_factory_new ();
415 priv->user = empathy_contact_factory_get_user (priv->factory, priv->account);
416 priv->mc = empathy_mission_control_new ();
418 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
419 TP_IFACE_QUARK_CHANNEL_TYPE_TEXT);
420 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
421 TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE);
422 priv->props_iface = tp_chan_get_interface (priv->tp_chan,
423 TP_IFACE_QUARK_PROPERTIES_INTERFACE);
425 g_signal_connect (priv->tp_chan, "destroy",
426 G_CALLBACK (tp_chat_destroy_cb),
428 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
429 G_CALLBACK (tp_chat_closed_cb),
431 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
432 G_CALLBACK (tp_chat_received_cb),
434 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
435 G_CALLBACK (tp_chat_sent_cb),
437 dbus_g_proxy_connect_signal (priv->text_iface, "SendError",
438 G_CALLBACK (tp_chat_send_error_cb),
441 if (priv->chat_state_iface != NULL) {
442 dbus_g_proxy_connect_signal (priv->chat_state_iface,
444 G_CALLBACK (tp_chat_state_changed_cb),
447 if (priv->props_iface != NULL) {
448 tp_props_iface_set_mapping (TELEPATHY_PROPS_IFACE (priv->props_iface),
449 "anonymous", PROP_ANONYMOUS,
450 "invite-only", PROP_INVITE_ONLY,
452 "limited", PROP_LIMITED,
453 "moderated", PROP_MODERATED,
455 "description", PROP_DESCRIPTION,
456 "password", PROP_PASSWORD,
457 "password-required", PROP_PASSWORD_REQUIRED,
458 "persistent", PROP_PERSISTENT,
459 "private", PROP_PRIVATE,
460 "subject", PROP_SUBJECT,
461 "subject-contact", PROP_SUBJECT_CONTACT,
462 "subject-timestamp", PROP_SUBJECT_TIMESTAMP,
464 g_signal_connect (priv->props_iface, "properties-ready",
465 G_CALLBACK (tp_chat_properties_ready_cb),
467 g_signal_connect (priv->props_iface, "properties-changed",
468 G_CALLBACK (tp_chat_properties_changed_cb),
476 tp_chat_get_property (GObject *object,
481 EmpathyTpChatPriv *priv;
484 priv = GET_PRIV (object);
485 chat = EMPATHY_TP_CHAT (object);
487 if (param_id >= PROP_ANONYMOUS &&
488 param_id <= PROP_SUBJECT_TIMESTAMP) {
489 if (priv->props_iface) {
490 tp_props_iface_get_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
500 g_value_set_object (value, priv->account);
503 g_value_set_object (value, priv->tp_chan);
505 case PROP_ACKNOWLEDGE:
506 g_value_set_boolean (value, priv->acknowledge);
509 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
515 tp_chat_set_property (GObject *object,
520 EmpathyTpChatPriv *priv;
523 priv = GET_PRIV (object);
524 chat = EMPATHY_TP_CHAT (object);
526 if (param_id >= PROP_ANONYMOUS &&
527 param_id <= PROP_SUBJECT_TIMESTAMP) {
528 if (priv->props_iface) {
529 tp_props_iface_set_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
539 priv->account = g_object_ref (g_value_get_object (value));
542 priv->tp_chan = g_object_ref (g_value_get_object (value));
544 case PROP_ACKNOWLEDGE:
545 empathy_tp_chat_set_acknowledge (EMPATHY_TP_CHAT (object),
546 g_value_get_boolean (value));
549 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
555 empathy_tp_chat_new (McAccount *account,
558 return g_object_new (EMPATHY_TYPE_TP_CHAT,
565 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
572 const gchar *bus_name;
575 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
577 mc = empathy_mission_control_new ();
578 account = empathy_contact_get_account (contact);
580 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
581 /* The account is not connected. */
585 tp_conn = mission_control_get_connection (mc, account, NULL);
586 g_return_val_if_fail (tp_conn != NULL, NULL);
587 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
588 handle = empathy_contact_get_handle (contact);
590 text_chan = tp_conn_new_channel (tp_get_bus (),
593 TP_IFACE_CHANNEL_TYPE_TEXT,
594 TP_HANDLE_TYPE_CONTACT,
598 chat = empathy_tp_chat_new (account, text_chan);
600 g_object_unref (tp_conn);
601 g_object_unref (text_chan);
608 empathy_tp_chat_get_acknowledge (EmpathyTpChat *chat)
610 EmpathyTpChatPriv *priv;
612 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
614 priv = GET_PRIV (chat);
616 return priv->acknowledge;
620 empathy_tp_chat_set_acknowledge (EmpathyTpChat *chat,
621 gboolean acknowledge)
623 EmpathyTpChatPriv *priv;
625 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
627 priv = GET_PRIV (chat);
629 priv->acknowledge = acknowledge;
630 g_object_notify (G_OBJECT (chat), "acknowledge");
634 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
636 EmpathyTpChatPriv *priv;
638 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
640 priv = GET_PRIV (chat);
642 return priv->tp_chan;
646 empathy_tp_chat_get_account (EmpathyTpChat *chat)
648 EmpathyTpChatPriv *priv;
650 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
652 priv = GET_PRIV (chat);
654 return priv->account;
658 empathy_tp_chat_get_pendings (EmpathyTpChat *chat)
660 EmpathyTpChatPriv *priv;
661 GPtrArray *messages_list;
663 GList *messages = NULL;
664 GError *error = NULL;
666 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
668 priv = GET_PRIV (chat);
670 /* If we do this call async, don't forget to ignore Received signal
671 * until we get the answer */
672 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
676 empathy_debug (DEBUG_DOMAIN,
677 "Error retrieving pending messages: %s",
678 error ? error->message : "No error given");
679 g_clear_error (&error);
683 for (i = 0; i < messages_list->len; i++) {
684 EmpathyMessage *message;
685 GValueArray *message_struct;
686 const gchar *message_body;
693 message_struct = g_ptr_array_index (messages_list, i);
695 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
696 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
697 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
698 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
699 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
700 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
702 empathy_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
704 message = tp_chat_build_message (chat,
710 messages = g_list_prepend (messages, message);
712 g_value_array_free (message_struct);
714 messages = g_list_reverse (messages);
716 g_ptr_array_free (messages_list, TRUE);
722 empathy_tp_chat_send (EmpathyTpChat *chat,
723 EmpathyMessage *message)
725 EmpathyTpChatPriv *priv;
726 const gchar *message_body;
727 EmpathyMessageType message_type;
728 GError *error = NULL;
730 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
731 g_return_if_fail (EMPATHY_IS_MESSAGE (message));
733 priv = GET_PRIV (chat);
735 message_body = empathy_message_get_body (message);
736 message_type = empathy_message_get_type (message);
738 empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
739 if (!tp_chan_type_text_send (priv->text_iface,
743 empathy_debug (DEBUG_DOMAIN,
745 error ? error->message : "No error given");
746 g_clear_error (&error);
751 empathy_tp_chat_set_state (EmpathyTpChat *chat,
752 TpChannelChatState state)
754 EmpathyTpChatPriv *priv;
755 GError *error = NULL;
757 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
759 priv = GET_PRIV (chat);
761 if (priv->chat_state_iface) {
762 empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
763 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
766 empathy_debug (DEBUG_DOMAIN,
767 "Set Chat State Error: %s",
768 error ? error->message : "No error given");
769 g_clear_error (&error);
775 empathy_tp_chat_get_id (EmpathyTpChat *chat)
777 EmpathyTpChatPriv *priv;
779 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
781 priv = GET_PRIV (chat);
784 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
791 tp_chat_destroy_cb (TpChan *text_chan,
794 EmpathyTpChatPriv *priv;
796 priv = GET_PRIV (chat);
798 empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
800 g_object_unref (priv->tp_chan);
801 priv->tp_chan = NULL;
802 priv->text_iface = NULL;
803 priv->chat_state_iface = NULL;
804 priv->props_iface = NULL;
806 g_signal_emit (chat, signals[DESTROY], 0);
810 tp_chat_closed_cb (TpChan *text_chan,
813 EmpathyTpChatPriv *priv;
815 priv = GET_PRIV (chat);
817 /* The channel is closed, do just like if the proxy was destroyed */
818 g_signal_handlers_disconnect_by_func (priv->tp_chan,
821 tp_chat_destroy_cb (text_chan, chat);
825 tp_chat_received_cb (DBusGProxy *text_iface,
834 EmpathyTpChatPriv *priv;
835 EmpathyMessage *message;
837 priv = GET_PRIV (chat);
839 empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
841 message = tp_chat_build_message (chat,
847 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
848 g_object_unref (message);
850 if (priv->acknowledge) {
853 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
854 g_array_append_val (message_ids, message_id);
855 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
857 g_array_free (message_ids, TRUE);
862 tp_chat_sent_cb (DBusGProxy *text_iface,
868 EmpathyMessage *message;
870 empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
872 message = tp_chat_build_message (chat,
878 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
879 g_object_unref (message);
883 tp_chat_send_error_cb (DBusGProxy *text_iface,
890 EmpathyMessage *message;
892 empathy_debug (DEBUG_DOMAIN, "Message sent error: %s (%d)",
893 message_body, error_code);
895 message = tp_chat_build_message (chat,
901 g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
902 g_object_unref (message);
906 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
908 TpChannelChatState state,
911 EmpathyTpChatPriv *priv;
912 EmpathyContact *contact;
914 priv = GET_PRIV (chat);
916 contact = empathy_contact_factory_get_from_handle (priv->factory,
920 empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
921 empathy_contact_get_name (contact),
925 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
926 g_object_unref (contact);
929 static EmpathyMessage *
930 tp_chat_build_message (EmpathyTpChat *chat,
934 const gchar *message_body)
936 EmpathyTpChatPriv *priv;
937 EmpathyMessage *message;
938 EmpathyContact *sender;
940 priv = GET_PRIV (chat);
942 if (from_handle == 0) {
943 sender = g_object_ref (priv->user);
945 sender = empathy_contact_factory_get_from_handle (priv->factory,
950 message = empathy_message_new (message_body);
951 empathy_message_set_type (message, type);
952 empathy_message_set_sender (message, sender);
953 empathy_message_set_receiver (message, priv->user);
954 empathy_message_set_timestamp (message, timestamp);
956 g_object_unref (sender);
962 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
965 g_object_notify (G_OBJECT (chat), "anonymous");
966 g_object_notify (G_OBJECT (chat), "invite-only");
967 g_object_notify (G_OBJECT (chat), "limit");
968 g_object_notify (G_OBJECT (chat), "limited");
969 g_object_notify (G_OBJECT (chat), "moderated");
970 g_object_notify (G_OBJECT (chat), "name");
971 g_object_notify (G_OBJECT (chat), "description");
972 g_object_notify (G_OBJECT (chat), "password");
973 g_object_notify (G_OBJECT (chat), "password-required");
974 g_object_notify (G_OBJECT (chat), "persistent");
975 g_object_notify (G_OBJECT (chat), "private");
976 g_object_notify (G_OBJECT (chat), "subject");
977 g_object_notify (G_OBJECT (chat), "subject-contact");
978 g_object_notify (G_OBJECT (chat), "subject-timestamp");
982 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
989 g_object_notify (G_OBJECT (chat), "anonymous");
991 case PROP_INVITE_ONLY:
992 g_object_notify (G_OBJECT (chat), "invite-only");
995 g_object_notify (G_OBJECT (chat), "limit");
998 g_object_notify (G_OBJECT (chat), "limited");
1000 case PROP_MODERATED:
1001 g_object_notify (G_OBJECT (chat), "moderated");
1004 g_object_notify (G_OBJECT (chat), "name");
1006 case PROP_DESCRIPTION:
1007 g_object_notify (G_OBJECT (chat), "description");
1010 g_object_notify (G_OBJECT (chat), "password");
1012 case PROP_PASSWORD_REQUIRED:
1013 g_object_notify (G_OBJECT (chat), "password-required");
1015 case PROP_PERSISTENT:
1016 g_object_notify (G_OBJECT (chat), "persistent");
1019 g_object_notify (G_OBJECT (chat), "private");
1022 g_object_notify (G_OBJECT (chat), "subject");
1024 case PROP_SUBJECT_CONTACT:
1025 g_object_notify (G_OBJECT (chat), "subject-contact");
1027 case PROP_SUBJECT_TIMESTAMP:
1028 g_object_notify (G_OBJECT (chat), "subject-timestamp");