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 "gossip-debug.h"
38 #include "gossip-time.h"
39 #include "gossip-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;
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_state_changed_cb (DBusGProxy *chat_state_iface,
92 TelepathyChannelChatState state,
94 static void tp_chat_emit_message (EmpathyTpChat *chat,
98 const gchar *message_body);
99 static void tp_chat_properties_ready_cb (TpPropsIface *props_iface,
100 EmpathyTpChat *chat);
101 static void tp_chat_properties_changed_cb (TpPropsIface *props_iface,
104 EmpathyTpChat *chat);
118 PROP_PASSWORD_REQUIRED,
122 PROP_SUBJECT_CONTACT,
123 PROP_SUBJECT_TIMESTAMP
133 static guint signals[LAST_SIGNAL];
135 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
138 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
140 GObjectClass *object_class = G_OBJECT_CLASS (klass);
142 object_class->finalize = tp_chat_finalize;
143 object_class->constructor = tp_chat_constructor;
144 object_class->get_property = tp_chat_get_property;
145 object_class->set_property = tp_chat_set_property;
147 /* Construct-only properties */
148 g_object_class_install_property (object_class,
150 g_param_spec_object ("account",
152 "The account associated with the channel",
155 G_PARAM_CONSTRUCT_ONLY));
156 g_object_class_install_property (object_class,
158 g_param_spec_object ("tp-chan",
160 "The text channel for the chat",
163 G_PARAM_CONSTRUCT_ONLY));
165 /* Properties of Text Channel */
166 g_object_class_install_property (object_class,
168 g_param_spec_boolean ("anonymous",
173 g_object_class_install_property (object_class,
175 g_param_spec_boolean ("invite-only",
180 g_object_class_install_property (object_class,
182 g_param_spec_uint ("limit",
189 g_object_class_install_property (object_class,
191 g_param_spec_boolean ("limited",
196 g_object_class_install_property (object_class,
198 g_param_spec_boolean ("moderated",
203 g_object_class_install_property (object_class,
205 g_param_spec_string ("name",
210 g_object_class_install_property (object_class,
212 g_param_spec_string ("description",
217 g_object_class_install_property (object_class,
219 g_param_spec_string ("password",
224 g_object_class_install_property (object_class,
225 PROP_PASSWORD_REQUIRED,
226 g_param_spec_boolean ("password-required",
231 g_object_class_install_property (object_class,
233 g_param_spec_boolean ("persistent",
238 g_object_class_install_property (object_class,
240 g_param_spec_boolean ("private",
246 g_object_class_install_property (object_class,
248 g_param_spec_string ("subject",
253 g_object_class_install_property (object_class,
254 PROP_SUBJECT_CONTACT,
255 g_param_spec_uint ("subject-contact",
262 g_object_class_install_property (object_class,
263 PROP_SUBJECT_TIMESTAMP,
264 g_param_spec_uint ("subject-timestamp",
273 signals[MESSAGE_RECEIVED] =
274 g_signal_new ("message-received",
275 G_TYPE_FROM_CLASS (klass),
279 g_cclosure_marshal_VOID__OBJECT,
281 1, GOSSIP_TYPE_MESSAGE);
283 signals[CHAT_STATE_CHANGED] =
284 g_signal_new ("chat-state-changed",
285 G_TYPE_FROM_CLASS (klass),
289 empathy_marshal_VOID__OBJECT_UINT,
291 2, GOSSIP_TYPE_CONTACT, G_TYPE_UINT);
294 g_signal_new ("destroy",
295 G_TYPE_FROM_CLASS (klass),
299 g_cclosure_marshal_VOID__VOID,
303 g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
307 empathy_tp_chat_init (EmpathyTpChat *chat)
313 tp_chat_finalize (GObject *object)
315 EmpathyTpChatPriv *priv;
317 GError *error = NULL;
319 chat = EMPATHY_TP_CHAT (object);
320 priv = GET_PRIV (chat);
323 gossip_debug (DEBUG_DOMAIN, "Closing channel...");
325 g_signal_handlers_disconnect_by_func (priv->tp_chan,
329 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
330 gossip_debug (DEBUG_DOMAIN,
331 "Error closing text channel: %s",
332 error ? error->message : "No error given");
333 g_clear_error (&error);
335 g_object_unref (priv->tp_chan);
339 g_object_unref (priv->manager);
342 g_object_unref (priv->list);
345 g_object_unref (priv->account);
348 g_object_unref (priv->mc);
352 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
356 tp_chat_constructor (GType type,
358 GObjectConstructParam *props)
361 EmpathyTpChatPriv *priv;
363 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
365 priv = GET_PRIV (chat);
367 priv->manager = empathy_contact_manager_new ();
368 priv->list = empathy_contact_manager_get_list (priv->manager, priv->account);
369 priv->mc = gossip_mission_control_new ();
370 g_object_ref (priv->list);
372 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
373 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
374 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
375 TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
376 priv->props_iface = tp_chan_get_interface (priv->tp_chan,
377 TELEPATHY_PROPS_IFACE_QUARK);
379 g_signal_connect (priv->tp_chan, "destroy",
380 G_CALLBACK (tp_chat_destroy_cb),
382 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
383 G_CALLBACK (tp_chat_closed_cb),
385 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
386 G_CALLBACK (tp_chat_received_cb),
388 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
389 G_CALLBACK (tp_chat_sent_cb),
392 if (priv->chat_state_iface != NULL) {
393 dbus_g_proxy_connect_signal (priv->chat_state_iface,
395 G_CALLBACK (tp_chat_state_changed_cb),
398 if (priv->props_iface != NULL) {
399 tp_props_iface_set_mapping (TELEPATHY_PROPS_IFACE (priv->props_iface),
400 "anonymous", PROP_ANONYMOUS,
401 "invite-only", PROP_INVITE_ONLY,
403 "limited", PROP_LIMITED,
404 "moderated", PROP_MODERATED,
406 "description", PROP_DESCRIPTION,
407 "password", PROP_PASSWORD,
408 "password-required", PROP_PASSWORD_REQUIRED,
409 "persistent", PROP_PERSISTENT,
410 "private", PROP_PRIVATE,
411 "subject", PROP_SUBJECT,
412 "subject-contact", PROP_SUBJECT_CONTACT,
413 "subject-timestamp", PROP_SUBJECT_TIMESTAMP,
415 g_signal_connect (priv->props_iface, "properties-ready",
416 G_CALLBACK (tp_chat_properties_ready_cb),
418 g_signal_connect (priv->props_iface, "properties-changed",
419 G_CALLBACK (tp_chat_properties_changed_cb),
427 tp_chat_get_property (GObject *object,
432 EmpathyTpChatPriv *priv;
435 priv = GET_PRIV (object);
436 chat = EMPATHY_TP_CHAT (object);
438 if (param_id >= PROP_ANONYMOUS &&
439 param_id <= PROP_SUBJECT_TIMESTAMP) {
440 if (priv->props_iface) {
441 tp_props_iface_get_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
451 g_value_set_object (value, priv->account);
454 g_value_set_object (value, priv->tp_chan);
457 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
463 tp_chat_set_property (GObject *object,
468 EmpathyTpChatPriv *priv;
471 priv = GET_PRIV (object);
472 chat = EMPATHY_TP_CHAT (object);
474 if (param_id >= PROP_ANONYMOUS &&
475 param_id <= PROP_SUBJECT_TIMESTAMP) {
476 if (priv->props_iface) {
477 tp_props_iface_set_value (TELEPATHY_PROPS_IFACE (priv->props_iface),
487 priv->account = g_object_ref (g_value_get_object (value));
490 priv->tp_chan = g_object_ref (g_value_get_object (value));
493 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
499 empathy_tp_chat_new (McAccount *account,
502 return g_object_new (EMPATHY_TYPE_TP_CHAT,
509 empathy_tp_chat_new_with_contact (GossipContact *contact)
516 const gchar *bus_name;
519 g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
521 mc = gossip_mission_control_new ();
522 account = gossip_contact_get_account (contact);
524 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
525 /* The account is not connected, nothing to do. */
529 tp_conn = mission_control_get_connection (mc, account, NULL);
530 g_return_val_if_fail (tp_conn != NULL, NULL);
531 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
532 handle = gossip_contact_get_handle (contact);
534 text_chan = tp_conn_new_channel (tp_get_bus (),
537 TP_IFACE_CHANNEL_TYPE_TEXT,
538 TP_HANDLE_TYPE_CONTACT,
542 chat = empathy_tp_chat_new (account, text_chan);
544 g_object_unref (tp_conn);
545 g_object_unref (text_chan);
552 empathy_tp_chat_request_pending (EmpathyTpChat *chat)
554 EmpathyTpChatPriv *priv;
555 GPtrArray *messages_list;
557 GError *error = NULL;
559 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
561 priv = GET_PRIV (chat);
563 /* If we do this call async, don't forget to ignore Received signal
564 * until we get the answer */
565 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
569 gossip_debug (DEBUG_DOMAIN,
570 "Error retrieving pending messages: %s",
571 error ? error->message : "No error given");
572 g_clear_error (&error);
576 for (i = 0; i < messages_list->len; i++) {
577 GValueArray *message_struct;
578 const gchar *message_body;
585 message_struct = g_ptr_array_index (messages_list, i);
587 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
588 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
589 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
590 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
591 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
592 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
594 gossip_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
596 tp_chat_emit_message (chat,
602 g_value_array_free (message_struct);
605 g_ptr_array_free (messages_list, TRUE);
609 empathy_tp_chat_send (EmpathyTpChat *chat,
610 GossipMessage *message)
612 EmpathyTpChatPriv *priv;
613 const gchar *message_body;
614 GossipMessageType message_type;
615 GError *error = NULL;
617 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
618 g_return_if_fail (GOSSIP_IS_MESSAGE (message));
620 priv = GET_PRIV (chat);
622 message_body = gossip_message_get_body (message);
623 message_type = gossip_message_get_type (message);
625 gossip_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
626 if (!tp_chan_type_text_send (priv->text_iface,
630 gossip_debug (DEBUG_DOMAIN,
632 error ? error->message : "No error given");
633 g_clear_error (&error);
638 empathy_tp_chat_set_state (EmpathyTpChat *chat,
639 TelepathyChannelChatState state)
641 EmpathyTpChatPriv *priv;
642 GError *error = NULL;
644 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
646 priv = GET_PRIV (chat);
648 if (priv->chat_state_iface) {
649 gossip_debug (DEBUG_DOMAIN, "Set state: %d", state);
650 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
653 gossip_debug (DEBUG_DOMAIN,
654 "Set Chat State Error: %s",
655 error ? error->message : "No error given");
656 g_clear_error (&error);
662 empathy_tp_chat_get_id (EmpathyTpChat *chat)
664 EmpathyTpChatPriv *priv;
666 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
668 priv = GET_PRIV (chat);
674 priv->id = gossip_get_channel_id (priv->account, priv->tp_chan);
680 tp_chat_destroy_cb (TpChan *text_chan,
683 EmpathyTpChatPriv *priv;
685 priv = GET_PRIV (chat);
687 gossip_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
689 g_object_unref (priv->tp_chan);
690 priv->tp_chan = NULL;
691 priv->text_iface = NULL;
692 priv->chat_state_iface = NULL;
693 priv->props_iface = NULL;
695 g_signal_emit (chat, signals[DESTROY], 0);
699 tp_chat_closed_cb (TpChan *text_chan,
702 EmpathyTpChatPriv *priv;
704 priv = GET_PRIV (chat);
706 /* The channel is closed, do just like if the proxy was destroyed */
707 g_signal_handlers_disconnect_by_func (priv->tp_chan,
710 tp_chat_destroy_cb (text_chan, chat);
715 tp_chat_received_cb (DBusGProxy *text_iface,
724 EmpathyTpChatPriv *priv;
727 priv = GET_PRIV (chat);
729 gossip_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
731 tp_chat_emit_message (chat,
737 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
738 g_array_append_val (message_ids, message_id);
739 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
741 g_array_free (message_ids, TRUE);
745 tp_chat_sent_cb (DBusGProxy *text_iface,
751 gossip_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
753 tp_chat_emit_message (chat,
761 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
763 TelepathyChannelChatState state,
766 EmpathyTpChatPriv *priv;
767 GossipContact *contact;
769 priv = GET_PRIV (chat);
771 contact = empathy_tp_contact_list_get_from_handle (priv->list, handle);
773 gossip_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
774 gossip_contact_get_name (contact),
778 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
780 g_object_unref (contact);
784 tp_chat_emit_message (EmpathyTpChat *chat,
788 const gchar *message_body)
790 EmpathyTpChatPriv *priv;
791 GossipMessage *message;
792 GossipContact *sender;
793 GossipContact *receiver;
795 priv = GET_PRIV (chat);
797 receiver = empathy_tp_contact_list_get_user (priv->list);
798 if (from_handle == 0) {
799 sender = g_object_ref (receiver);
801 sender = empathy_tp_contact_list_get_from_handle (priv->list,
805 message = gossip_message_new (message_body);
806 gossip_message_set_type (message, type);
807 gossip_message_set_sender (message, sender);
808 gossip_message_set_receiver (message, receiver);
809 gossip_message_set_timestamp (message, (GossipTime) timestamp);
811 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
813 g_object_unref (message);
814 g_object_unref (sender);
818 tp_chat_properties_ready_cb (TpPropsIface *props_iface,
821 g_object_notify (G_OBJECT (chat), "anonymous");
822 g_object_notify (G_OBJECT (chat), "invite-only");
823 g_object_notify (G_OBJECT (chat), "limit");
824 g_object_notify (G_OBJECT (chat), "limited");
825 g_object_notify (G_OBJECT (chat), "moderated");
826 g_object_notify (G_OBJECT (chat), "name");
827 g_object_notify (G_OBJECT (chat), "description");
828 g_object_notify (G_OBJECT (chat), "password");
829 g_object_notify (G_OBJECT (chat), "password-required");
830 g_object_notify (G_OBJECT (chat), "persistent");
831 g_object_notify (G_OBJECT (chat), "private");
832 g_object_notify (G_OBJECT (chat), "subject");
833 g_object_notify (G_OBJECT (chat), "subject-contact");
834 g_object_notify (G_OBJECT (chat), "subject-timestamp");
838 tp_chat_properties_changed_cb (TpPropsIface *props_iface,
845 g_object_notify (G_OBJECT (chat), "anonymous");
847 case PROP_INVITE_ONLY:
848 g_object_notify (G_OBJECT (chat), "invite-only");
851 g_object_notify (G_OBJECT (chat), "limit");
854 g_object_notify (G_OBJECT (chat), "limited");
857 g_object_notify (G_OBJECT (chat), "moderated");
860 g_object_notify (G_OBJECT (chat), "name");
862 case PROP_DESCRIPTION:
863 g_object_notify (G_OBJECT (chat), "description");
866 g_object_notify (G_OBJECT (chat), "password");
868 case PROP_PASSWORD_REQUIRED:
869 g_object_notify (G_OBJECT (chat), "password-required");
871 case PROP_PERSISTENT:
872 g_object_notify (G_OBJECT (chat), "persistent");
875 g_object_notify (G_OBJECT (chat), "private");
878 g_object_notify (G_OBJECT (chat), "subject");
880 case PROP_SUBJECT_CONTACT:
881 g_object_notify (G_OBJECT (chat), "subject-contact");
883 case PROP_SUBJECT_TIMESTAMP:
884 g_object_notify (G_OBJECT (chat), "subject-timestamp");