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_state_changed_cb (DBusGProxy *chat_state_iface,
93 TelepathyChannelChatState state,
95 static EmpathyMessage * tp_chat_build_message (EmpathyTpChat *chat,
99 const gchar *message_body);
100 static void tp_chat_properties_ready_cb (TpPropsIface *props_iface,
101 EmpathyTpChat *chat);
102 static void tp_chat_properties_changed_cb (TpPropsIface *props_iface,
105 EmpathyTpChat *chat);
120 PROP_PASSWORD_REQUIRED,
124 PROP_SUBJECT_CONTACT,
125 PROP_SUBJECT_TIMESTAMP
135 static guint signals[LAST_SIGNAL];
137 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
140 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
142 GObjectClass *object_class = G_OBJECT_CLASS (klass);
144 object_class->finalize = tp_chat_finalize;
145 object_class->constructor = tp_chat_constructor;
146 object_class->get_property = tp_chat_get_property;
147 object_class->set_property = tp_chat_set_property;
149 /* Construct-only properties */
150 g_object_class_install_property (object_class,
152 g_param_spec_object ("account",
154 "The account associated with the channel",
157 G_PARAM_CONSTRUCT_ONLY));
158 g_object_class_install_property (object_class,
160 g_param_spec_object ("tp-chan",
162 "The text channel for the chat",
165 G_PARAM_CONSTRUCT_ONLY));
167 /* Normal properties */
168 g_object_class_install_property (object_class,
170 g_param_spec_boolean ("acknowledge",
176 /* Properties of Text Channel */
177 g_object_class_install_property (object_class,
179 g_param_spec_boolean ("anonymous",
184 g_object_class_install_property (object_class,
186 g_param_spec_boolean ("invite-only",
191 g_object_class_install_property (object_class,
193 g_param_spec_uint ("limit",
200 g_object_class_install_property (object_class,
202 g_param_spec_boolean ("limited",
207 g_object_class_install_property (object_class,
209 g_param_spec_boolean ("moderated",
214 g_object_class_install_property (object_class,
216 g_param_spec_string ("name",
221 g_object_class_install_property (object_class,
223 g_param_spec_string ("description",
228 g_object_class_install_property (object_class,
230 g_param_spec_string ("password",
235 g_object_class_install_property (object_class,
236 PROP_PASSWORD_REQUIRED,
237 g_param_spec_boolean ("password-required",
242 g_object_class_install_property (object_class,
244 g_param_spec_boolean ("persistent",
249 g_object_class_install_property (object_class,
251 g_param_spec_boolean ("private",
257 g_object_class_install_property (object_class,
259 g_param_spec_string ("subject",
264 g_object_class_install_property (object_class,
265 PROP_SUBJECT_CONTACT,
266 g_param_spec_uint ("subject-contact",
273 g_object_class_install_property (object_class,
274 PROP_SUBJECT_TIMESTAMP,
275 g_param_spec_uint ("subject-timestamp",
284 signals[MESSAGE_RECEIVED] =
285 g_signal_new ("message-received",
286 G_TYPE_FROM_CLASS (klass),
290 g_cclosure_marshal_VOID__OBJECT,
292 1, EMPATHY_TYPE_MESSAGE);
294 signals[CHAT_STATE_CHANGED] =
295 g_signal_new ("chat-state-changed",
296 G_TYPE_FROM_CLASS (klass),
300 empathy_marshal_VOID__OBJECT_UINT,
302 2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
305 g_signal_new ("destroy",
306 G_TYPE_FROM_CLASS (klass),
310 g_cclosure_marshal_VOID__VOID,
314 g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
318 empathy_tp_chat_init (EmpathyTpChat *chat)
324 tp_chat_finalize (GObject *object)
326 EmpathyTpChatPriv *priv;
328 GError *error = NULL;
330 chat = EMPATHY_TP_CHAT (object);
331 priv = GET_PRIV (chat);
334 g_signal_handlers_disconnect_by_func (priv->tp_chan,
337 if (priv->acknowledge) {
338 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
339 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
340 empathy_debug (DEBUG_DOMAIN,
341 "Error closing text channel: %s",
342 error ? error->message : "No error given");
343 g_clear_error (&error);
346 g_object_unref (priv->tp_chan);
350 g_object_unref (priv->manager);
353 g_object_unref (priv->list);
356 g_object_unref (priv->account);
359 g_object_unref (priv->mc);
363 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
367 tp_chat_constructor (GType type,
369 GObjectConstructParam *props)
372 EmpathyTpChatPriv *priv;
374 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
376 priv = GET_PRIV (chat);
378 priv->manager = empathy_contact_manager_new ();
379 priv->list = empathy_contact_manager_get_list (priv->manager, priv->account);
380 priv->mc = empathy_mission_control_new ();
381 g_object_ref (priv->list);
383 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
384 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
385 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
386 TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
387 priv->props_iface = tp_chan_get_interface (priv->tp_chan,
388 TELEPATHY_PROPS_IFACE_QUARK);
390 g_signal_connect (priv->tp_chan, "destroy",
391 G_CALLBACK (tp_chat_destroy_cb),
393 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
394 G_CALLBACK (tp_chat_closed_cb),
396 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
397 G_CALLBACK (tp_chat_received_cb),
399 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
400 G_CALLBACK (tp_chat_sent_cb),
403 if (priv->chat_state_iface != NULL) {
404 dbus_g_proxy_connect_signal (priv->chat_state_iface,
406 G_CALLBACK (tp_chat_state_changed_cb),
409 if (priv->props_iface != NULL) {
410 tp_props_iface_set_mapping (TELEPATHY_PROPS_IFACE (priv->props_iface),
411 "anonymous", PROP_ANONYMOUS,
412 "invite-only", PROP_INVITE_ONLY,
414 "limited", PROP_LIMITED,
415 "moderated", PROP_MODERATED,
417 "description", PROP_DESCRIPTION,
418 "password", PROP_PASSWORD,
419 "password-required", PROP_PASSWORD_REQUIRED,
420 "persistent", PROP_PERSISTENT,
421 "private", PROP_PRIVATE,
422 "subject", PROP_SUBJECT,
423 "subject-contact", PROP_SUBJECT_CONTACT,
424 "subject-timestamp", PROP_SUBJECT_TIMESTAMP,
426 g_signal_connect (priv->props_iface, "properties-ready",
427 G_CALLBACK (tp_chat_properties_ready_cb),
429 g_signal_connect (priv->props_iface, "properties-changed",
430 G_CALLBACK (tp_chat_properties_changed_cb),
438 tp_chat_get_property (GObject *object,
443 EmpathyTpChatPriv *priv;
446 priv = GET_PRIV (object);
447 chat = EMPATHY_TP_CHAT (object);
449 if (param_id >= PROP_ANONYMOUS &&
450 param_id <= PROP_SUBJECT_TIMESTAMP) {
451 if (priv->props_iface) {
452 tp_props_iface_get_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
462 g_value_set_object (value, priv->account);
465 g_value_set_object (value, priv->tp_chan);
467 case PROP_ACKNOWLEDGE:
468 g_value_set_boolean (value, priv->acknowledge);
471 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
477 tp_chat_set_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_set_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
501 priv->account = g_object_ref (g_value_get_object (value));
504 priv->tp_chan = g_object_ref (g_value_get_object (value));
506 case PROP_ACKNOWLEDGE:
507 empathy_tp_chat_set_acknowledge (EMPATHY_TP_CHAT (object),
508 g_value_get_boolean (value));
511 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
517 empathy_tp_chat_new (McAccount *account,
520 return g_object_new (EMPATHY_TYPE_TP_CHAT,
527 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
534 const gchar *bus_name;
537 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
539 mc = empathy_mission_control_new ();
540 account = empathy_contact_get_account (contact);
542 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
543 /* The account is not connected, nothing to do. */
547 tp_conn = mission_control_get_connection (mc, account, NULL);
548 g_return_val_if_fail (tp_conn != NULL, NULL);
549 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
550 handle = empathy_contact_get_handle (contact);
552 text_chan = tp_conn_new_channel (tp_get_bus (),
555 TP_IFACE_CHANNEL_TYPE_TEXT,
556 TP_HANDLE_TYPE_CONTACT,
560 chat = empathy_tp_chat_new (account, text_chan);
562 g_object_unref (tp_conn);
563 g_object_unref (text_chan);
570 empathy_tp_chat_get_acknowledge (EmpathyTpChat *chat)
572 EmpathyTpChatPriv *priv;
574 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
576 priv = GET_PRIV (chat);
578 return priv->acknowledge;
582 empathy_tp_chat_set_acknowledge (EmpathyTpChat *chat,
583 gboolean acknowledge)
585 EmpathyTpChatPriv *priv;
587 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
589 priv = GET_PRIV (chat);
591 priv->acknowledge = acknowledge;
592 g_object_notify (G_OBJECT (chat), "acknowledge");
596 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
598 EmpathyTpChatPriv *priv;
600 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
602 priv = GET_PRIV (chat);
604 return priv->tp_chan;
608 empathy_tp_chat_get_pendings (EmpathyTpChat *chat)
610 EmpathyTpChatPriv *priv;
611 GPtrArray *messages_list;
613 GList *messages = NULL;
614 GError *error = NULL;
616 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
618 priv = GET_PRIV (chat);
620 /* If we do this call async, don't forget to ignore Received signal
621 * until we get the answer */
622 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
626 empathy_debug (DEBUG_DOMAIN,
627 "Error retrieving pending messages: %s",
628 error ? error->message : "No error given");
629 g_clear_error (&error);
633 for (i = 0; i < messages_list->len; i++) {
634 EmpathyMessage *message;
635 GValueArray *message_struct;
636 const gchar *message_body;
643 message_struct = g_ptr_array_index (messages_list, i);
645 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
646 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
647 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
648 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
649 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
650 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
652 empathy_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
654 message = tp_chat_build_message (chat,
660 messages = g_list_prepend (messages, message);
662 g_value_array_free (message_struct);
665 g_ptr_array_free (messages_list, TRUE);
671 empathy_tp_chat_send (EmpathyTpChat *chat,
672 EmpathyMessage *message)
674 EmpathyTpChatPriv *priv;
675 const gchar *message_body;
676 EmpathyMessageType message_type;
677 GError *error = NULL;
679 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
680 g_return_if_fail (EMPATHY_IS_MESSAGE (message));
682 priv = GET_PRIV (chat);
684 message_body = empathy_message_get_body (message);
685 message_type = empathy_message_get_type (message);
687 empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
688 if (!tp_chan_type_text_send (priv->text_iface,
692 empathy_debug (DEBUG_DOMAIN,
694 error ? error->message : "No error given");
695 g_clear_error (&error);
700 empathy_tp_chat_set_state (EmpathyTpChat *chat,
701 TelepathyChannelChatState state)
703 EmpathyTpChatPriv *priv;
704 GError *error = NULL;
706 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
708 priv = GET_PRIV (chat);
710 if (priv->chat_state_iface) {
711 empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
712 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
715 empathy_debug (DEBUG_DOMAIN,
716 "Set Chat State Error: %s",
717 error ? error->message : "No error given");
718 g_clear_error (&error);
724 empathy_tp_chat_get_id (EmpathyTpChat *chat)
726 EmpathyTpChatPriv *priv;
728 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
730 priv = GET_PRIV (chat);
736 priv->id = empathy_get_channel_id (priv->account, priv->tp_chan);
742 tp_chat_destroy_cb (TpChan *text_chan,
745 EmpathyTpChatPriv *priv;
747 priv = GET_PRIV (chat);
749 empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
751 g_object_unref (priv->tp_chan);
752 priv->tp_chan = NULL;
753 priv->text_iface = NULL;
754 priv->chat_state_iface = NULL;
755 priv->props_iface = NULL;
757 g_signal_emit (chat, signals[DESTROY], 0);
761 tp_chat_closed_cb (TpChan *text_chan,
764 EmpathyTpChatPriv *priv;
766 priv = GET_PRIV (chat);
768 /* The channel is closed, do just like if the proxy was destroyed */
769 g_signal_handlers_disconnect_by_func (priv->tp_chan,
772 tp_chat_destroy_cb (text_chan, chat);
777 tp_chat_received_cb (DBusGProxy *text_iface,
786 EmpathyTpChatPriv *priv;
787 EmpathyMessage *message;
789 priv = GET_PRIV (chat);
791 empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
793 message = tp_chat_build_message (chat,
799 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
800 g_object_unref (message);
802 if (priv->acknowledge) {
805 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
806 g_array_append_val (message_ids, message_id);
807 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
809 g_array_free (message_ids, TRUE);
814 tp_chat_sent_cb (DBusGProxy *text_iface,
820 EmpathyMessage *message;
822 empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
824 message = tp_chat_build_message (chat,
830 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
831 g_object_unref (message);
835 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
837 TelepathyChannelChatState state,
840 EmpathyTpChatPriv *priv;
841 EmpathyContact *contact;
843 priv = GET_PRIV (chat);
845 contact = empathy_tp_contact_list_get_from_handle (priv->list, handle);
847 empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
848 empathy_contact_get_name (contact),
852 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
853 g_object_unref (contact);
856 static EmpathyMessage *
857 tp_chat_build_message (EmpathyTpChat *chat,
861 const gchar *message_body)
863 EmpathyTpChatPriv *priv;
864 EmpathyMessage *message;
865 EmpathyContact *sender;
866 EmpathyContact *receiver;
868 priv = GET_PRIV (chat);
870 receiver = empathy_tp_contact_list_get_user (priv->list);
871 if (from_handle == 0) {
872 sender = g_object_ref (receiver);
874 sender = empathy_tp_contact_list_get_from_handle (priv->list,
878 message = empathy_message_new (message_body);
879 empathy_message_set_type (message, type);
880 empathy_message_set_sender (message, sender);
881 empathy_message_set_receiver (message, receiver);
882 empathy_message_set_timestamp (message, (EmpathyTime) timestamp);
884 g_object_unref (sender);
890 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
893 g_object_notify (G_OBJECT (chat), "anonymous");
894 g_object_notify (G_OBJECT (chat), "invite-only");
895 g_object_notify (G_OBJECT (chat), "limit");
896 g_object_notify (G_OBJECT (chat), "limited");
897 g_object_notify (G_OBJECT (chat), "moderated");
898 g_object_notify (G_OBJECT (chat), "name");
899 g_object_notify (G_OBJECT (chat), "description");
900 g_object_notify (G_OBJECT (chat), "password");
901 g_object_notify (G_OBJECT (chat), "password-required");
902 g_object_notify (G_OBJECT (chat), "persistent");
903 g_object_notify (G_OBJECT (chat), "private");
904 g_object_notify (G_OBJECT (chat), "subject");
905 g_object_notify (G_OBJECT (chat), "subject-contact");
906 g_object_notify (G_OBJECT (chat), "subject-timestamp");
910 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
917 g_object_notify (G_OBJECT (chat), "anonymous");
919 case PROP_INVITE_ONLY:
920 g_object_notify (G_OBJECT (chat), "invite-only");
923 g_object_notify (G_OBJECT (chat), "limit");
926 g_object_notify (G_OBJECT (chat), "limited");
929 g_object_notify (G_OBJECT (chat), "moderated");
932 g_object_notify (G_OBJECT (chat), "name");
934 case PROP_DESCRIPTION:
935 g_object_notify (G_OBJECT (chat), "description");
938 g_object_notify (G_OBJECT (chat), "password");
940 case PROP_PASSWORD_REQUIRED:
941 g_object_notify (G_OBJECT (chat), "password-required");
943 case PROP_PERSISTENT:
944 g_object_notify (G_OBJECT (chat), "persistent");
947 g_object_notify (G_OBJECT (chat), "private");
950 g_object_notify (G_OBJECT (chat), "subject");
952 case PROP_SUBJECT_CONTACT:
953 g_object_notify (G_OBJECT (chat), "subject-contact");
955 case PROP_SUBJECT_TIMESTAMP:
956 g_object_notify (G_OBJECT (chat), "subject-timestamp");