]> git.0d.be Git - empathy.git/blob - src/empathy-filter.c
Correct spacing in some strings. Fix bug #529437 (Baptiste Mille-Mathias).
[empathy.git] / src / empathy-filter.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  *
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.
9  *
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.
14  *
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
18  * 
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25
26 #include <glib/gi18n.h>
27
28 #include <telepathy-glib/enums.h>
29 #include <telepathy-glib/channel.h>
30 #include <telepathy-glib/connection.h>
31 #include <telepathy-glib/util.h>
32 #include <telepathy-glib/dbus.h>
33 #include <telepathy-glib/proxy-subclass.h>
34
35 #include <libmissioncontrol/mission-control.h>
36 #include <libmissioncontrol/mc-account.h>
37
38 #include <extensions/extensions.h>
39
40 #include <libempathy/empathy-tp-chat.h>
41 #include <libempathy/empathy-tp-call.h>
42 #include <libempathy/empathy-tp-group.h>
43 #include <libempathy/empathy-utils.h>
44 #include <libempathy/empathy-debug.h>
45 #include <libempathy/empathy-tube-handler.h>
46 #include <libempathy/empathy-contact-factory.h>
47
48 #include <libempathy-gtk/empathy-chat.h>
49 #include <libempathy-gtk/empathy-images.h>
50 #include <libempathy-gtk/empathy-contact-dialogs.h>
51
52 #include "empathy-filter.h"
53 #include "empathy-chat-window.h"
54 #include "empathy-call-window.h"
55
56 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
57                        EMPATHY_TYPE_FILTER, EmpathyFilterPriv))
58
59 #define DEBUG_DOMAIN "Filter"
60
61 struct _EmpathyFilterPriv {
62         GSList         *events;
63         GHashTable     *accounts;
64         gpointer        token;
65         MissionControl *mc;
66         GHashTable     *tubes;
67 };
68
69 static void empathy_filter_class_init (EmpathyFilterClass *klass);
70 static void empathy_filter_init       (EmpathyFilter      *filter);
71
72 G_DEFINE_TYPE (EmpathyFilter, empathy_filter, G_TYPE_OBJECT);
73
74 enum {
75         PROP_0,
76         PROP_TOP_EVENT,
77 };
78
79 typedef void (*FilterFunc) (EmpathyFilter *filter,
80                             gpointer       user_data);
81
82 typedef struct {
83         EmpathyFilterEvent public;
84         FilterFunc         func;
85         gpointer           user_data;
86 } EmpathyFilterEventExt;
87
88 static guint
89 filter_channel_hash (gconstpointer key)
90 {
91         TpProxy *channel = TP_PROXY (key);
92
93         return g_str_hash (channel->object_path);
94 }
95
96 static gboolean
97 filter_channel_equal (gconstpointer a,
98                       gconstpointer b)
99 {
100         TpProxy *channel_a = TP_PROXY (a);
101         TpProxy *channel_b = TP_PROXY (b);
102
103         return g_str_equal (channel_a->object_path, channel_b->object_path);
104 }
105
106 static void
107 filter_event_free (EmpathyFilterEventExt *event)
108 {
109         g_free (event->public.icon_name);
110         g_free (event->public.message);
111         g_slice_free (EmpathyFilterEventExt, event);
112 }
113
114 static void
115 filter_emit_event (EmpathyFilter *filter,
116                    const gchar   *icon_name,
117                    const gchar   *message,
118                    FilterFunc     func,
119                    gpointer       user_data)
120 {
121         EmpathyFilterPriv     *priv = GET_PRIV (filter);
122         EmpathyFilterEventExt *event;
123
124         empathy_debug (DEBUG_DOMAIN, "Emit event, icon_name=%s message='%s'",
125                        icon_name, message);
126
127         event = g_slice_new0 (EmpathyFilterEventExt);
128         event->func = func;
129         event->user_data = user_data;
130         event->public.icon_name = g_strdup (icon_name);
131         event->public.message = g_strdup (message);
132
133         priv->events = g_slist_append (priv->events, event);
134         if (priv->events->data == event) {
135                 g_object_notify (G_OBJECT (filter), "top-event");
136         }
137 }
138
139 void
140 empathy_filter_activate_event (EmpathyFilter      *filter,
141                                EmpathyFilterEvent *event)
142 {
143         EmpathyFilterPriv     *priv = GET_PRIV (filter);
144         EmpathyFilterEventExt *event_ext;
145         GSList                *l;
146         gboolean               is_top;
147
148         g_return_if_fail (EMPATHY_IS_FILTER (filter));
149         g_return_if_fail (event != NULL);
150
151         if (!(l = g_slist_find (priv->events, event))) {
152                 return;
153         }
154
155         empathy_debug (DEBUG_DOMAIN, "Activating event");
156
157         is_top = (l == priv->events);
158         priv->events = g_slist_delete_link (priv->events, l);
159         if (is_top) {
160                 g_object_notify (G_OBJECT (filter), "top-event");
161         }
162
163         event_ext = (EmpathyFilterEventExt*) event;
164         if (event_ext->func) {
165                 event_ext->func (filter, event_ext->user_data);
166         }
167
168         filter_event_free (event_ext);
169 }
170
171 EmpathyFilterEvent *
172 empathy_filter_get_top_event (EmpathyFilter *filter)
173 {
174         EmpathyFilterPriv *priv = GET_PRIV (filter);
175
176         g_return_val_if_fail (EMPATHY_IS_FILTER (filter), NULL);
177
178         return priv->events ? priv->events->data : NULL;
179 }
180
181 static void
182 filter_chat_dispatch (EmpathyFilter *filter,
183                       gpointer       user_data)
184 {
185         EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
186         EmpathyChat   *chat = NULL;
187         const gchar   *id;
188
189         id = empathy_tp_chat_get_id (tp_chat);
190         if (!id) {
191                 EmpathyContact *contact;
192
193                 contact = empathy_tp_chat_get_remote_contact (tp_chat);
194                 if (contact) {
195                         id = empathy_contact_get_id (contact);
196                 }
197         }
198
199         if (id) {
200                 McAccount *account;
201
202                 account = empathy_tp_chat_get_account (tp_chat);
203                 chat = empathy_chat_window_find_chat (account, id);
204         }
205
206         if (chat) {
207                 empathy_chat_set_tp_chat (chat, tp_chat);
208         } else {
209                 chat = empathy_chat_new (tp_chat);
210         }
211
212         empathy_chat_window_present_chat (chat);
213         g_object_unref (tp_chat);
214 }
215
216 static void
217 filter_chat_message_received_cb (EmpathyTpChat   *tp_chat,
218                                  EmpathyMessage  *message,
219                                  EmpathyFilter   *filter)
220 {
221         EmpathyContact  *sender;
222         gchar           *msg;
223
224         g_signal_handlers_disconnect_by_func (tp_chat,
225                                               filter_chat_message_received_cb,
226                                               filter);
227
228         sender = empathy_message_get_sender (message);
229         msg = g_strdup_printf (_("New message from %s:\n%s"),
230                                empathy_contact_get_name (sender),
231                                empathy_message_get_body (message));
232
233         filter_emit_event (filter, EMPATHY_IMAGE_NEW_MESSAGE, msg,
234                            filter_chat_dispatch, tp_chat);
235
236         g_free (msg);
237 }
238
239 static void
240 filter_chat_handle_channel (EmpathyFilter *filter,
241                             TpChannel     *channel,
242                             gboolean       is_incoming)
243 {
244         EmpathyTpChat *tp_chat;
245
246         empathy_debug (DEBUG_DOMAIN, "New text channel to be filtered: %p",
247                        channel);
248
249         tp_chat = empathy_tp_chat_new (channel, FALSE);
250         empathy_run_until_ready (tp_chat);
251         if (is_incoming) {
252                 filter_chat_dispatch (filter, tp_chat);
253         } else {
254                 g_signal_connect (tp_chat, "message-received",
255                                   G_CALLBACK (filter_chat_message_received_cb),
256                                   filter);
257         }
258 }
259
260 static void
261 filter_call_dispatch (EmpathyFilter *filter,
262                       gpointer       user_data)
263 {
264         EmpathyTpCall *call = EMPATHY_TP_CALL (user_data);
265
266         empathy_call_window_new (call);
267         g_object_unref (call);
268 }
269
270 static void
271 filter_call_contact_notify_cb (EmpathyTpCall *call,
272                                gpointer       unused,
273                                EmpathyFilter *filter)
274 {
275         EmpathyContact *contact;
276         gchar          *msg;
277
278         g_object_get (call, "contact", &contact, NULL);
279         if (!contact) {
280                 return;
281         }
282
283         empathy_contact_run_until_ready (contact,
284                                          EMPATHY_CONTACT_READY_NAME,
285                                          NULL);
286
287         msg = g_strdup_printf (_("Incoming call from %s"),
288                                empathy_contact_get_name (contact));
289
290         filter_emit_event (filter, EMPATHY_IMAGE_VOIP, msg,
291                            filter_call_dispatch, call);
292
293         g_free (msg);
294         g_object_unref (contact);
295 }
296
297 static void
298 filter_call_handle_channel (EmpathyFilter *filter,
299                             TpChannel     *channel,
300                             gboolean       is_incoming)
301 {
302         EmpathyTpCall *call;
303
304         empathy_debug (DEBUG_DOMAIN, "New media channel to be filtered: %p",
305                        channel);
306
307         call = empathy_tp_call_new (channel);
308         if (is_incoming) {
309                 filter_call_dispatch (filter, call);
310         } else {
311                 g_signal_connect (call, "notify::contact",
312                                   G_CALLBACK (filter_call_contact_notify_cb),
313                                   filter);      
314         }
315 }
316
317 static void
318 filter_contact_list_subscribe (EmpathyFilter *filter,
319                                gpointer       user_data)
320 {
321         EmpathyContact *contact = EMPATHY_CONTACT (user_data);
322
323         empathy_subscription_dialog_show (contact, NULL);
324         g_object_unref (contact);
325 }
326
327 static void
328 filter_contact_list_local_pending_cb (EmpathyTpGroup *group,
329                                       EmpathyContact *contact,
330                                       EmpathyContact *actor,
331                                       guint           reason,
332                                       gchar          *message,
333                                       EmpathyFilter  *filter)
334 {
335         GString *str;
336
337         empathy_debug (DEBUG_DOMAIN, "New local pending contact");
338
339         empathy_contact_run_until_ready (contact,
340                                          EMPATHY_CONTACT_READY_NAME,
341                                          NULL);
342
343         str = g_string_new (NULL);
344         g_string_printf (str, _("Subscription requested by %s"),
345                          empathy_contact_get_name (contact));   
346         if (!G_STR_EMPTY (message)) {
347                 g_string_append_printf (str, _("\nMessage: %s"), message);
348         }
349
350         filter_emit_event (filter, GTK_STOCK_DIALOG_QUESTION, str->str,
351                            filter_contact_list_subscribe,
352                            g_object_ref (contact));
353
354         g_string_free (str, TRUE);
355 }
356
357 static void
358 filter_contact_list_ready_cb (EmpathyTpGroup *group,
359                               gpointer        unused,
360                               EmpathyFilter  *filter)
361 {
362         GList *pendings, *l;
363
364         if (tp_strdiff ("publish", empathy_tp_group_get_name (group))) {
365                 g_object_unref (group);
366                 return;
367         }
368
369         empathy_debug (DEBUG_DOMAIN, "Publish contact list ready");
370
371         g_signal_connect (group, "local-pending",
372                           G_CALLBACK (filter_contact_list_local_pending_cb),
373                           filter);
374
375         pendings = empathy_tp_group_get_local_pendings (group);
376         for (l = pendings; l; l = l->next) {
377                 EmpathyPendingInfo *info = l->data;
378
379                 filter_contact_list_local_pending_cb (group, info->member,
380                                                       info->actor, info->reason,
381                                                       info->message, filter);
382                 empathy_pending_info_free (info);
383         }
384         g_list_free (pendings);
385 }
386
387 static void
388 filter_tubes_async_cb (TpProxy      *channel,
389                        const GError *error,
390                        gpointer      user_data,
391                        GObject      *filter)
392 {
393         if (error) {
394                 empathy_debug (DEBUG_DOMAIN, "Error %s: %s",
395                                user_data, error->message);
396         }
397 }
398
399 typedef struct {
400         EmpathyContactFactory *factory;
401         EmpathyContact        *initiator;
402         TpChannel             *channel;
403         guint                  id;
404         gchar                 *bus_name;
405         gchar                 *object_path;
406         gboolean               activatable;
407 } FilterTubesData;
408
409 static void
410 filter_tubes_dispatch (EmpathyFilter *filter,
411                        gpointer       user_data)
412 {
413         FilterTubesData *data = user_data;
414
415         if (data->activatable) {
416                 TpProxy *connection;
417                 TpProxy *thandler;
418                 gchar   *object_path;
419                 guint    handle_type;
420                 guint    handle;
421
422                 /* Create the proxy for the tube handler */
423                 thandler = g_object_new (TP_TYPE_PROXY,
424                                          "dbus-connection", tp_get_bus (),
425                                          "bus-name", data->bus_name,
426                                          "object-path", data->object_path,
427                                          NULL);
428                 tp_proxy_add_interface_by_id (thandler, EMP_IFACE_QUARK_TUBE_HANDLER);
429
430                 /* Give the tube to the handler */
431                 g_object_get (data->channel,
432                               "connection", &connection,
433                               "object-path", &object_path,
434                               "handle_type", &handle_type,
435                               "handle", &handle,
436                               NULL);
437
438                 empathy_debug (DEBUG_DOMAIN, "Dispatching tube");
439                 emp_cli_tube_handler_call_handle_tube (thandler, -1,
440                                                        connection->bus_name,
441                                                        connection->object_path,
442                                                        object_path, handle_type,
443                                                        handle, data->id,
444                                                        filter_tubes_async_cb,
445                                                        "handling tube", NULL,
446                                                        G_OBJECT (filter));
447
448                 g_object_unref (thandler);
449                 g_object_unref (connection);
450                 g_free (object_path);
451         } else {
452                 GtkWidget *dialog;
453                 gchar     *str;
454
455                 /* Tell the user that the tube can't be handled */
456                 str = g_strdup_printf (_("%s offered you an invitation, but "
457                                          "you don't have the needed external "
458                                          "application to handle it."),
459                                        empathy_contact_get_name (data->initiator));
460
461                 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
462                                                  GTK_MESSAGE_ERROR,
463                                                  GTK_BUTTONS_OK, str);
464                 gtk_window_set_title (GTK_WINDOW (dialog),
465                                       _("Invitation Error"));
466                 g_free (str);
467
468                 gtk_widget_show (dialog);
469                 g_signal_connect (dialog, "response",
470                                   G_CALLBACK (gtk_widget_destroy),
471                                   NULL);
472
473                 empathy_debug (DEBUG_DOMAIN, "Tube can't be handled, closing");
474                 tp_cli_channel_type_tubes_call_close_tube (data->channel, -1,
475                                                            data->id,
476                                                            NULL, NULL, NULL,
477                                                            NULL);
478         }
479
480         g_free (data->bus_name);
481         g_free (data->object_path);
482         g_object_unref (data->channel);
483         g_object_unref (data->initiator);
484         g_object_unref (data->factory);
485         g_slice_free (FilterTubesData, data);
486 }
487
488 static void
489 filter_tubes_new_tube_cb (TpChannel   *channel,
490                           guint        id,
491                           guint        initiator,
492                           guint        type,
493                           const gchar *service,
494                           GHashTable  *parameters,
495                           guint        state,
496                           gpointer     user_data,
497                           GObject     *filter)
498 {
499         EmpathyFilterPriv *priv = GET_PRIV (filter);
500         static TpDBusDaemon *daemon = NULL;
501         FilterTubesData   *data;
502         McAccount         *account;
503         guint              number;
504         gchar             *msg;
505         gchar            **names;
506         gboolean           running = FALSE;
507         const gchar       *icon_name;
508         GError            *error = NULL;
509
510         /* Increase tube count */
511         number = GPOINTER_TO_UINT (g_hash_table_lookup (priv->tubes, channel));
512         g_hash_table_replace (priv->tubes, g_object_ref (channel),
513                               GUINT_TO_POINTER (++number));
514         empathy_debug (DEBUG_DOMAIN, "Increased tube count for channel %p: %d",
515                        channel, number);
516
517         /* We dispatch only local pending tubes */
518         if (state != TP_TUBE_STATE_LOCAL_PENDING) {
519                 return;
520         }
521
522         if (!daemon) {
523                 daemon = tp_dbus_daemon_new (tp_get_bus ());
524         }
525
526         account = empathy_channel_get_account (channel);
527         data = g_slice_new (FilterTubesData);
528         data->activatable = FALSE;
529         data->id = id;
530         data->channel = g_object_ref (channel);
531         data->factory = empathy_contact_factory_new ();
532         data->initiator = empathy_contact_factory_get_from_handle (data->factory,
533                                                                    account,
534                                                                    initiator);
535         data->bus_name = empathy_tube_handler_build_bus_name (type, service);
536         data->object_path = empathy_tube_handler_build_object_path (type, service);
537         g_object_unref (account);
538
539         /* Check if that bus-name has an owner, if it has one that means the
540          * app is already running and we can directly give the channel. */
541         tp_cli_dbus_daemon_run_name_has_owner (daemon, -1, data->bus_name,
542                                                &running, NULL, NULL);
543         if (running) {
544                 empathy_debug (DEBUG_DOMAIN, "Tube handler running");
545                 data->activatable = TRUE;
546                 filter_tubes_dispatch (EMPATHY_FILTER (filter), data);
547                 return;
548         }
549
550         /* Check if that bus-name is activatable, if not that means the
551          * application needed to handle this tube isn't installed. */
552         if (!tp_cli_dbus_daemon_run_list_activatable_names (daemon, -1,
553                                                             &names, &error,
554                                                             NULL)) {
555                 empathy_debug (DEBUG_DOMAIN, "Error listing activatable names: %s",
556                                error->message);
557                 g_clear_error (&error);
558         } else {
559                 gchar **name;
560
561                 for (name = names; *name; name++) {
562                         if (!tp_strdiff (*name, data->bus_name)) {
563                                 data->activatable = TRUE;
564                                 break;
565                         }
566                 }
567                 g_strfreev (names);
568         }
569
570         empathy_contact_run_until_ready (data->initiator,
571                                          EMPATHY_CONTACT_READY_NAME, NULL);
572
573         if (data->activatable) {
574                 icon_name = GTK_STOCK_EXECUTE;
575                 msg = g_strdup_printf (_("%s is offering you an invitation. An external "
576                                          "application will be started to handle it."),
577                                        empathy_contact_get_name (data->initiator));
578         } else {
579                 icon_name = GTK_STOCK_DIALOG_ERROR;
580                 msg = g_strdup_printf (_("%s is offering you an invitation, but "
581                                          "you don't have the needed external "
582                                          "application to handle it."),
583                                        empathy_contact_get_name (data->initiator));
584         }
585
586         filter_emit_event (EMPATHY_FILTER (filter), icon_name, msg,
587                            filter_tubes_dispatch, data);
588
589         g_free (msg);
590 }
591
592 static void
593 filter_tubes_list_tubes_cb (TpChannel       *channel,
594                             const GPtrArray *tubes,
595                             const GError    *error,
596                             gpointer         user_data,
597                             GObject         *filter)
598 {
599         guint i;
600
601         if (error) {
602                 empathy_debug (DEBUG_DOMAIN, "Error listing tubes: %s",
603                                error->message);
604                 return;
605         }
606
607         for (i = 0; i < tubes->len; i++) {
608                 GValueArray *values;
609
610                 values = g_ptr_array_index (tubes, i);
611                 filter_tubes_new_tube_cb (channel,
612                                           g_value_get_uint (g_value_array_get_nth (values, 0)),
613                                           g_value_get_uint (g_value_array_get_nth (values, 1)),
614                                           g_value_get_uint (g_value_array_get_nth (values, 2)),
615                                           g_value_get_string (g_value_array_get_nth (values, 3)),
616                                           g_value_get_boxed (g_value_array_get_nth (values, 4)),
617                                           g_value_get_uint (g_value_array_get_nth (values, 5)),
618                                           user_data, filter);
619         }
620 }
621
622 static void
623 filter_tubes_channel_invalidated_cb (TpProxy       *proxy,
624                                      guint          domain,
625                                      gint           code,
626                                      gchar         *message,
627                                      EmpathyFilter *filter)
628 {
629         EmpathyFilterPriv *priv = GET_PRIV (filter);
630
631         empathy_debug (DEBUG_DOMAIN, "Channel %p invalidated: %s", proxy, message);
632
633         g_hash_table_remove (priv->tubes, proxy);
634 }
635
636 static void
637 filter_tubes_tube_closed_cb (TpChannel *channel,
638                              guint      id,
639                              gpointer   user_data,
640                              GObject   *filter)
641 {
642         EmpathyFilterPriv *priv = GET_PRIV (filter);
643         guint              number;
644
645         number = GPOINTER_TO_UINT (g_hash_table_lookup (priv->tubes, channel));
646         if (number == 1) {
647                 empathy_debug (DEBUG_DOMAIN, "Ended tube count for channel %p, "
648                                "closing channel", channel);
649                 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
650         }
651         else if (number > 1) {
652                 empathy_debug (DEBUG_DOMAIN, "Decrease tube count for channel %p: %d",
653                                channel, number);
654                 g_hash_table_replace (priv->tubes, g_object_ref (channel),
655                                       GUINT_TO_POINTER (--number));
656         }
657 }
658
659 static void
660 filter_tubes_handle_channel (EmpathyFilter *filter,
661                              TpChannel     *channel,
662                              gboolean       is_incoming)
663 {
664         EmpathyFilterPriv *priv = GET_PRIV (filter);
665
666         if (g_hash_table_lookup (priv->tubes, channel)) {
667                 return;
668         }
669
670         empathy_debug (DEBUG_DOMAIN, "Handling new channel");
671
672         g_hash_table_insert (priv->tubes, g_object_ref (channel),
673                              GUINT_TO_POINTER (0));
674
675         g_signal_connect (channel, "invalidated",
676                           G_CALLBACK (filter_tubes_channel_invalidated_cb),
677                           filter);
678
679         tp_cli_channel_type_tubes_connect_to_tube_closed (channel,
680                                                           filter_tubes_tube_closed_cb,
681                                                           NULL, NULL,
682                                                           G_OBJECT (filter), NULL);
683         tp_cli_channel_type_tubes_connect_to_new_tube (channel,
684                                                        filter_tubes_new_tube_cb,
685                                                        NULL, NULL,
686                                                        G_OBJECT (filter), NULL);
687         tp_cli_channel_type_tubes_call_list_tubes (channel, -1,
688                                                    filter_tubes_list_tubes_cb,
689                                                    NULL, NULL,
690                                                    G_OBJECT (filter));
691 }
692
693 static void
694 filter_contact_list_destroy_cb (EmpathyTpGroup *group,
695                                 EmpathyFilter  *filter)
696 {
697         g_object_unref (group);
698 }
699
700 static void
701 filter_contact_list_handle_channel (EmpathyFilter *filter,
702                                     TpChannel     *channel,
703                                     gboolean       is_incoming)
704 {
705         EmpathyTpGroup *group;
706
707         group = empathy_tp_group_new (channel);
708         g_signal_connect (group, "notify::ready",
709                           G_CALLBACK (filter_contact_list_ready_cb),
710                           filter);      
711         g_signal_connect (group, "destroy",
712                           G_CALLBACK (filter_contact_list_destroy_cb),
713                           filter);
714 }
715
716 static void
717 filter_connection_invalidated_cb (TpConnection  *connection,
718                                   guint          domain,
719                                   gint           code,
720                                   gchar         *message,
721                                   EmpathyFilter *filter)
722 {
723         EmpathyFilterPriv *priv = GET_PRIV (filter);
724         GHashTableIter     iter;
725         gpointer           key, value;
726
727         empathy_debug (DEBUG_DOMAIN, "connection invalidated: %s", message);
728
729         g_hash_table_iter_init (&iter, priv->accounts);
730         while (g_hash_table_iter_next (&iter, &key, &value)) {
731                 if (value == connection) {
732                         g_hash_table_remove (priv->accounts, key);
733                         break;
734                 }
735         }
736 }
737
738 typedef void (*HandleChannelFunc) (EmpathyFilter *filter,
739                                    TpChannel     *channel,
740                                    gboolean       is_incoming);
741
742 static void
743 filter_conection_new_channel_cb (TpConnection *connection,
744                                  const gchar  *object_path,
745                                  const gchar  *channel_type,
746                                  guint         handle_type,
747                                  guint         handle,
748                                  gboolean      suppress_handler,
749                                  gpointer      user_data,
750                                  GObject      *filter)
751 {
752         HandleChannelFunc  func = NULL;
753         TpChannel         *channel;
754         gpointer           had_channels;
755         
756         had_channels = g_object_get_data (G_OBJECT (connection), "had-channels");
757         if (had_channels == NULL) {
758                 return;
759         }
760
761         if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)) {
762                 func = filter_chat_handle_channel;
763         }
764         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) {
765                 func = filter_call_handle_channel;
766         }
767         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) {
768                 func = filter_contact_list_handle_channel;
769         }
770         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES)) {
771                 func = filter_tubes_handle_channel;
772         } else {
773                 empathy_debug (DEBUG_DOMAIN, "Unknown channel type %s",
774                                channel_type);
775                 return;
776         }
777
778         channel = tp_channel_new (connection, object_path, channel_type,
779                                   handle_type, handle, NULL);
780         tp_channel_run_until_ready (channel, NULL, NULL);
781
782         /* We abuse of suppress_handler, TRUE means OUTGOING */
783         func (EMPATHY_FILTER (filter), channel, suppress_handler);
784
785         g_object_unref (channel);
786 }
787
788 static void
789 filter_connection_list_channels_cb (TpConnection    *connection,
790                                     const GPtrArray *channels,
791                                     const GError    *error,
792                                     gpointer         user_data,
793                                     GObject         *filter)
794 {
795         guint i;
796
797         if (error) {
798                 empathy_debug (DEBUG_DOMAIN, "Error listing channels: %s",
799                                error->message);
800                 return;
801         }
802
803         g_object_set_data (G_OBJECT (connection), "had-channels",
804                            GUINT_TO_POINTER (1));
805
806         for (i = 0; i < channels->len; i++) {
807                 GValueArray *values;
808
809                 values = g_ptr_array_index (channels, i);
810                 filter_conection_new_channel_cb (connection,
811                         g_value_get_boxed (g_value_array_get_nth (values, 0)),
812                         g_value_get_string (g_value_array_get_nth (values, 1)),
813                         g_value_get_uint (g_value_array_get_nth (values, 2)),
814                         g_value_get_uint (g_value_array_get_nth (values, 3)),
815                         TRUE, user_data, filter);
816         }
817 }
818
819 static void
820 filter_connection_advertise_capabilities_cb (TpConnection    *connection,
821                                              const GPtrArray *capabilities,
822                                              const GError    *error,
823                                              gpointer         user_data,
824                                              GObject         *filter)
825 {
826         if (error) {
827                 empathy_debug (DEBUG_DOMAIN, "Error advertising capabilities: %s",
828                                error->message);
829         }
830 }
831
832 static void
833 filter_connection_ready_cb (TpConnection  *connection,
834                             gpointer       unused,
835                             EmpathyFilter *filter)
836 {
837         GPtrArray   *capabilities;
838         GType        cap_type;
839         GValue       cap = {0, };
840         const gchar *remove = NULL;
841
842         empathy_debug (DEBUG_DOMAIN, "Connection ready, accepting new channels");
843
844         tp_cli_connection_connect_to_new_channel (connection,
845                                                   filter_conection_new_channel_cb,
846                                                   NULL, NULL,
847                                                   G_OBJECT (filter), NULL);
848         tp_cli_connection_call_list_channels (connection, -1,
849                                               filter_connection_list_channels_cb,
850                                               NULL, NULL,
851                                               G_OBJECT (filter));
852
853         /* Advertise VoIP capabilities */
854         capabilities = g_ptr_array_sized_new (1);
855         cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING,
856                                            G_TYPE_UINT, G_TYPE_INVALID);
857         g_value_init (&cap, cap_type);
858         g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type));
859         dbus_g_type_struct_set (&cap,
860                                 0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
861                                 1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
862                                    TP_CHANNEL_MEDIA_CAPABILITY_VIDEO |
863                                    TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN  |
864                                    TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P,
865                                 G_MAXUINT);
866         g_ptr_array_add (capabilities, g_value_get_boxed (&cap));
867
868         tp_cli_connection_interface_capabilities_call_advertise_capabilities (
869                 connection, -1,
870                 capabilities, &remove,
871                 filter_connection_advertise_capabilities_cb,
872                 NULL, NULL, G_OBJECT (filter));
873 }
874
875 static void
876 filter_update_account (EmpathyFilter *filter,
877                        McAccount     *account)
878 {
879         EmpathyFilterPriv *priv = GET_PRIV (filter);
880         TpConnection      *connection;
881         gboolean           ready;
882
883         connection = g_hash_table_lookup (priv->accounts, account);
884         if (connection) {
885                 return;
886         }
887
888         connection = mission_control_get_tpconnection (priv->mc, account, NULL);
889         if (!connection) {
890                 return;
891         }
892
893         g_hash_table_insert (priv->accounts, g_object_ref (account), connection);
894         g_signal_connect (connection, "invalidated",
895                           G_CALLBACK (filter_connection_invalidated_cb),
896                           filter);
897
898         g_object_get (connection, "connection-ready", &ready, NULL);
899         if (ready) {
900                 filter_connection_ready_cb (connection, NULL, filter);
901         } else {
902                 g_signal_connect (connection, "notify::connection-ready",
903                                   G_CALLBACK (filter_connection_ready_cb),
904                                   filter);
905         }
906 }
907
908 static void
909 filter_status_changed_cb (MissionControl           *mc,
910                           TpConnectionStatus        status,
911                           McPresence                presence,
912                           TpConnectionStatusReason  reason,
913                           const gchar              *unique_name,
914                           EmpathyFilter            *filter)
915 {
916         McAccount *account;
917
918         account = mc_account_lookup (unique_name);
919         filter_update_account (filter, account);
920         g_object_unref (account);
921 }
922
923 static void
924 filter_finalize (GObject *object)
925 {
926         EmpathyFilterPriv *priv = GET_PRIV (object);
927
928         empathy_disconnect_account_status_changed (priv->token);
929         g_object_unref (priv->mc);
930
931         g_slist_foreach (priv->events, (GFunc) filter_event_free, NULL);
932         g_slist_free (priv->events);
933
934         g_hash_table_destroy (priv->accounts);
935         g_hash_table_destroy (priv->tubes);
936 }
937
938 static void
939 filter_get_property (GObject    *object,
940                      guint       param_id,
941                      GValue     *value,
942                      GParamSpec *pspec)
943 {
944         EmpathyFilterPriv *priv = GET_PRIV (object);
945
946         switch (param_id) {
947         case PROP_TOP_EVENT:
948                 g_value_set_pointer (value, priv->events ? priv->events->data : NULL);
949                 break;
950         default:
951                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
952                 break;
953         };
954 }
955
956 static void
957 empathy_filter_class_init (EmpathyFilterClass *klass)
958 {
959         GObjectClass *object_class = G_OBJECT_CLASS (klass);
960
961         object_class->finalize = filter_finalize;
962         object_class->get_property = filter_get_property;
963
964         g_object_class_install_property (object_class,
965                                          PROP_TOP_EVENT,
966                                          g_param_spec_pointer ("top-event",
967                                                                "The top event",
968                                                                "The first event in the events list",
969                                                                G_PARAM_READABLE));
970
971         g_type_class_add_private (object_class, sizeof (EmpathyFilterPriv));
972 }
973
974 static void
975 empathy_filter_init (EmpathyFilter *filter)
976 {
977         EmpathyFilterPriv *priv = GET_PRIV (filter);
978         GList             *accounts, *l;
979
980         priv->tubes = g_hash_table_new_full (filter_channel_hash,
981                                              filter_channel_equal,
982                                              g_object_unref, NULL);
983
984         priv->mc = empathy_mission_control_new ();
985         priv->token = empathy_connect_to_account_status_changed (priv->mc,
986                 G_CALLBACK (filter_status_changed_cb),
987                 filter, NULL);
988
989         priv->accounts = g_hash_table_new_full (empathy_account_hash,
990                                                 empathy_account_equal,
991                                                 g_object_unref,
992                                                 g_object_unref);
993         accounts = mc_accounts_list_by_enabled (TRUE);
994         for (l = accounts; l; l = l->next) {
995                 filter_update_account (filter, l->data);
996                 g_object_unref (l->data);
997         }
998         g_list_free (accounts);
999 }
1000
1001 EmpathyFilter *
1002 empathy_filter_new (void)
1003 {
1004         static EmpathyFilter *filter = NULL;
1005
1006         if (!filter) {
1007                 filter = g_object_new (EMPATHY_TYPE_FILTER, NULL);
1008                 g_object_add_weak_pointer (G_OBJECT (filter), (gpointer) &filter);
1009         } else {
1010                 g_object_ref (filter);
1011         }
1012
1013         return filter;
1014 }
1015