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>
32 #include "empathy-tp-chat.h"
33 #include "empathy-contact-manager.h"
34 #include "empathy-tp-contact-list.h"
35 #include "empathy-marshal.h"
36 #include "gossip-debug.h"
37 #include "gossip-time.h"
38 #include "gossip-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 EmpathyTpContactList *list;
47 EmpathyContactManager *manager;
53 DBusGProxy *text_iface;
54 DBusGProxy *chat_state_iface;
57 static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass);
58 static void empathy_tp_chat_init (EmpathyTpChat *chat);
59 static void tp_chat_finalize (GObject *object);
60 static GObject * tp_chat_constructor (GType type,
62 GObjectConstructParam *props);
63 static void tp_chat_get_property (GObject *object,
67 static void tp_chat_set_property (GObject *object,
71 static void tp_chat_destroy_cb (TpChan *text_chan,
73 static void tp_chat_closed_cb (TpChan *text_chan,
75 static void tp_chat_received_cb (DBusGProxy *text_iface,
83 static void tp_chat_sent_cb (DBusGProxy *text_iface,
88 static void tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
90 TelepathyChannelChatState state,
92 static void tp_chat_emit_message (EmpathyTpChat *chat,
96 const gchar *message_body);
110 static guint signals[LAST_SIGNAL];
112 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
115 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
117 GObjectClass *object_class = G_OBJECT_CLASS (klass);
119 object_class->finalize = tp_chat_finalize;
120 object_class->constructor = tp_chat_constructor;
121 object_class->get_property = tp_chat_get_property;
122 object_class->set_property = tp_chat_set_property;
124 g_object_class_install_property (object_class,
126 g_param_spec_object ("account",
128 "The account associated with the channel",
131 G_PARAM_CONSTRUCT_ONLY));
133 g_object_class_install_property (object_class,
135 g_param_spec_object ("tp-chan",
137 "The text channel for the chat",
140 G_PARAM_CONSTRUCT_ONLY));
142 signals[MESSAGE_RECEIVED] =
143 g_signal_new ("message-received",
144 G_TYPE_FROM_CLASS (klass),
148 g_cclosure_marshal_VOID__OBJECT,
150 1, GOSSIP_TYPE_MESSAGE);
152 signals[CHAT_STATE_CHANGED] =
153 g_signal_new ("chat-state-changed",
154 G_TYPE_FROM_CLASS (klass),
158 empathy_marshal_VOID__OBJECT_UINT,
160 2, GOSSIP_TYPE_CONTACT, G_TYPE_UINT);
163 g_signal_new ("destroy",
164 G_TYPE_FROM_CLASS (klass),
168 g_cclosure_marshal_VOID__VOID,
172 g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
176 empathy_tp_chat_init (EmpathyTpChat *chat)
182 tp_chat_finalize (GObject *object)
184 EmpathyTpChatPriv *priv;
186 GError *error = NULL;
188 chat = EMPATHY_TP_CHAT (object);
189 priv = GET_PRIV (chat);
192 gossip_debug (DEBUG_DOMAIN, "Closing channel...");
194 g_signal_handlers_disconnect_by_func (priv->tp_chan,
198 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
199 gossip_debug (DEBUG_DOMAIN,
200 "Error closing text channel: %s",
201 error ? error->message : "No error given");
202 g_clear_error (&error);
204 g_object_unref (priv->tp_chan);
208 g_object_unref (priv->manager);
211 g_object_unref (priv->list);
214 g_object_unref (priv->account);
217 g_object_unref (priv->mc);
221 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
225 tp_chat_constructor (GType type,
227 GObjectConstructParam *props)
230 EmpathyTpChatPriv *priv;
232 chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
234 priv = GET_PRIV (chat);
236 priv->manager = empathy_contact_manager_new ();
237 priv->list = empathy_contact_manager_get_list (priv->manager, priv->account);
238 priv->mc = gossip_mission_control_new ();
239 g_object_ref (priv->list);
241 priv->text_iface = tp_chan_get_interface (priv->tp_chan,
242 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
243 priv->chat_state_iface = tp_chan_get_interface (priv->tp_chan,
244 TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
246 g_signal_connect (priv->tp_chan, "destroy",
247 G_CALLBACK (tp_chat_destroy_cb),
249 dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
250 G_CALLBACK (tp_chat_closed_cb),
252 dbus_g_proxy_connect_signal (priv->text_iface, "Received",
253 G_CALLBACK (tp_chat_received_cb),
255 dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
256 G_CALLBACK (tp_chat_sent_cb),
259 if (priv->chat_state_iface != NULL) {
260 dbus_g_proxy_connect_signal (priv->chat_state_iface,
262 G_CALLBACK (tp_chat_state_changed_cb),
270 tp_chat_get_property (GObject *object,
275 EmpathyTpChatPriv *priv;
277 priv = GET_PRIV (object);
281 g_value_set_object (value, priv->account);
284 g_value_set_object (value, priv->tp_chan);
287 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
293 tp_chat_set_property (GObject *object,
298 EmpathyTpChatPriv *priv;
300 priv = GET_PRIV (object);
304 priv->account = g_object_ref (g_value_get_object (value));
307 priv->tp_chan = g_object_ref (g_value_get_object (value));
310 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
316 empathy_tp_chat_new (McAccount *account,
319 return g_object_new (EMPATHY_TYPE_TP_CHAT,
326 empathy_tp_chat_new_with_contact (GossipContact *contact)
333 const gchar *bus_name;
336 g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
338 mc = gossip_mission_control_new ();
339 account = gossip_contact_get_account (contact);
341 if (mission_control_get_connection_status (mc, account, NULL) != 0) {
342 /* The account is not connected, nothing to do. */
346 tp_conn = mission_control_get_connection (mc, account, NULL);
347 g_return_val_if_fail (tp_conn != NULL, NULL);
348 bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
349 handle = gossip_contact_get_handle (contact);
351 text_chan = tp_conn_new_channel (tp_get_bus (),
354 TP_IFACE_CHANNEL_TYPE_TEXT,
355 TP_HANDLE_TYPE_CONTACT,
359 chat = empathy_tp_chat_new (account, text_chan);
361 g_object_unref (tp_conn);
362 g_object_unref (text_chan);
369 empathy_tp_chat_request_pending (EmpathyTpChat *chat)
371 EmpathyTpChatPriv *priv;
372 GPtrArray *messages_list;
374 GError *error = NULL;
376 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
378 priv = GET_PRIV (chat);
380 /* If we do this call async, don't forget to ignore Received signal
381 * until we get the answer */
382 if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
386 gossip_debug (DEBUG_DOMAIN,
387 "Error retrieving pending messages: %s",
388 error ? error->message : "No error given");
389 g_clear_error (&error);
393 for (i = 0; i < messages_list->len; i++) {
394 GValueArray *message_struct;
395 const gchar *message_body;
402 message_struct = g_ptr_array_index (messages_list, i);
404 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
405 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
406 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
407 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
408 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
409 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
411 gossip_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
413 tp_chat_emit_message (chat,
419 g_value_array_free (message_struct);
422 g_ptr_array_free (messages_list, TRUE);
426 empathy_tp_chat_send (EmpathyTpChat *chat,
427 GossipMessage *message)
429 EmpathyTpChatPriv *priv;
430 const gchar *message_body;
431 GossipMessageType message_type;
432 GError *error = NULL;
434 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
435 g_return_if_fail (GOSSIP_IS_MESSAGE (message));
437 priv = GET_PRIV (chat);
439 message_body = gossip_message_get_body (message);
440 message_type = gossip_message_get_type (message);
442 gossip_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
443 if (!tp_chan_type_text_send (priv->text_iface,
447 gossip_debug (DEBUG_DOMAIN,
449 error ? error->message : "No error given");
450 g_clear_error (&error);
455 empathy_tp_chat_set_state (EmpathyTpChat *chat,
456 TelepathyChannelChatState state)
458 EmpathyTpChatPriv *priv;
459 GError *error = NULL;
461 g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
463 priv = GET_PRIV (chat);
465 if (priv->chat_state_iface) {
466 gossip_debug (DEBUG_DOMAIN, "Set state: %d", state);
467 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
470 gossip_debug (DEBUG_DOMAIN,
471 "Set Chat State Error: %s",
472 error ? error->message : "No error given");
473 g_clear_error (&error);
479 empathy_tp_chat_get_id (EmpathyTpChat *chat)
481 EmpathyTpChatPriv *priv;
483 g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
485 priv = GET_PRIV (chat);
491 priv->id = empathy_tp_chat_build_id_for_chan (priv->account, priv->tp_chan);
497 empathy_tp_chat_build_id (McAccount *account,
498 const gchar *contact_id)
500 /* A handle name is unique only for a specific account */
501 return g_strdup_printf ("%s/%s",
502 mc_account_get_unique_name (account),
507 empathy_tp_chat_build_id_for_chan (McAccount *account,
515 GError *error = NULL;
517 g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
518 g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
520 mc = gossip_mission_control_new ();
521 tp_conn = mission_control_get_connection (mc, account, NULL);
524 /* Get the handle's name */
525 handles = g_array_new (FALSE, FALSE, sizeof (guint));
526 g_array_append_val (handles, tp_chan->handle);
527 if (!tp_conn_inspect_handles (DBUS_G_PROXY (tp_conn),
528 tp_chan->handle_type,
532 gossip_debug (DEBUG_DOMAIN,
533 "Couldn't get id: %s",
534 error ? error->message : "No error given");
535 g_clear_error (&error);
536 g_array_free (handles, TRUE);
537 g_object_unref (tp_conn);
542 id = empathy_tp_chat_build_id (account, *names);
545 g_object_unref (tp_conn);
551 tp_chat_destroy_cb (TpChan *text_chan,
554 EmpathyTpChatPriv *priv;
556 priv = GET_PRIV (chat);
558 gossip_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
560 g_object_unref (priv->tp_chan);
561 priv->tp_chan = NULL;
562 priv->text_iface = NULL;
563 priv->chat_state_iface = NULL;
565 g_signal_emit (chat, signals[DESTROY], 0);
569 tp_chat_closed_cb (TpChan *text_chan,
572 EmpathyTpChatPriv *priv;
574 priv = GET_PRIV (chat);
576 /* The channel is closed, do just like if the proxy was destroyed */
577 g_signal_handlers_disconnect_by_func (priv->tp_chan,
580 tp_chat_destroy_cb (text_chan, chat);
585 tp_chat_received_cb (DBusGProxy *text_iface,
594 EmpathyTpChatPriv *priv;
597 priv = GET_PRIV (chat);
599 gossip_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
601 tp_chat_emit_message (chat,
607 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
608 g_array_append_val (message_ids, message_id);
609 tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
611 g_array_free (message_ids, TRUE);
615 tp_chat_sent_cb (DBusGProxy *text_iface,
621 gossip_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
623 tp_chat_emit_message (chat,
631 tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
633 TelepathyChannelChatState state,
636 EmpathyTpChatPriv *priv;
637 GossipContact *contact;
639 priv = GET_PRIV (chat);
641 contact = empathy_tp_contact_list_get_from_handle (priv->list, handle);
643 gossip_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
644 gossip_contact_get_name (contact),
648 g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
650 g_object_unref (contact);
654 tp_chat_emit_message (EmpathyTpChat *chat,
658 const gchar *message_body)
660 EmpathyTpChatPriv *priv;
661 GossipMessage *message;
662 GossipContact *sender;
664 priv = GET_PRIV (chat);
666 if (from_handle == 0) {
667 sender = empathy_tp_contact_list_get_user (priv->list);
668 g_object_ref (sender);
670 sender = empathy_tp_contact_list_get_from_handle (priv->list,
674 message = gossip_message_new (message_body);
675 gossip_message_set_type (message, type);
676 gossip_message_set_sender (message, sender);
677 gossip_message_set_timestamp (message, (GossipTime) timestamp);
679 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
681 g_object_unref (message);
682 g_object_unref (sender);