]> git.0d.be Git - empathy.git/blob - src/empathy-filter.c
Ask user confirmation before dispatching an incoming tube
[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         McAccount     *account;
187         EmpathyChat   *chat;
188         const gchar   *id;
189
190         id = empathy_tp_chat_get_id (tp_chat);
191         account = empathy_tp_chat_get_account (tp_chat);
192         chat = empathy_chat_window_find_chat (account, id);
193
194         if (chat) {
195                 empathy_chat_set_tp_chat (chat, tp_chat);
196         } else {
197                 chat = empathy_chat_new (tp_chat);
198         }
199
200         empathy_chat_window_present_chat (chat);
201         g_object_unref (tp_chat);
202 }
203
204 static void
205 filter_chat_message_received_cb (EmpathyTpChat   *tp_chat,
206                                  EmpathyMessage  *message,
207                                  EmpathyFilter   *filter)
208 {
209         EmpathyContact  *sender;
210         gchar           *msg;
211
212         g_signal_handlers_disconnect_by_func (tp_chat,
213                                               filter_chat_message_received_cb,
214                                               filter);
215
216         sender = empathy_message_get_sender (message);
217         msg = g_strdup_printf (_("New message from %s:\n%s"),
218                                empathy_contact_get_name (sender),
219                                empathy_message_get_body (message));
220
221         filter_emit_event (filter, EMPATHY_IMAGE_NEW_MESSAGE, msg,
222                            filter_chat_dispatch, tp_chat);
223
224         g_free (msg);
225 }
226
227 static void
228 filter_chat_handle_channel (EmpathyFilter *filter,
229                             TpChannel     *channel,
230                             gboolean       is_incoming)
231 {
232         EmpathyTpChat *tp_chat;
233
234         empathy_debug (DEBUG_DOMAIN, "New text channel to be filtered: %p",
235                        channel);
236
237         tp_chat = empathy_tp_chat_new (channel, FALSE);
238         if (is_incoming) {
239                 filter_chat_dispatch (filter, tp_chat);
240         } else {
241                 g_signal_connect (tp_chat, "message-received",
242                                   G_CALLBACK (filter_chat_message_received_cb),
243                                   filter);
244         }
245 }
246
247 #ifdef HAVE_VOIP
248 static void
249 filter_call_dispatch (EmpathyFilter *filter,
250                       gpointer       user_data)
251 {
252         EmpathyTpCall *call = EMPATHY_TP_CALL (user_data);
253
254         empathy_call_window_new (call);
255         g_object_unref (call);
256 }
257
258 static void
259 filter_call_contact_notify_cb (EmpathyTpCall *call,
260                                gpointer       unused,
261                                EmpathyFilter *filter)
262 {
263         EmpathyContact *contact;
264         gchar          *msg;
265
266         g_object_get (call, "contact", &contact, NULL);
267         if (!contact) {
268                 return;
269         }
270
271         empathy_contact_run_until_ready (contact,
272                                          EMPATHY_CONTACT_READY_NAME,
273                                          NULL);
274
275         msg = g_strdup_printf (_("Incoming call from %s"),
276                                empathy_contact_get_name (contact));
277
278         filter_emit_event (filter, EMPATHY_IMAGE_VOIP, msg,
279                            filter_call_dispatch, call);
280
281         g_free (msg);
282         g_object_unref (contact);
283 }
284
285 static void
286 filter_call_handle_channel (EmpathyFilter *filter,
287                             TpChannel     *channel,
288                             gboolean       is_incoming)
289 {
290         EmpathyTpCall *call;
291
292         empathy_debug (DEBUG_DOMAIN, "New media channel to be filtered: %p",
293                        channel);
294
295         call = empathy_tp_call_new (channel);
296         if (is_incoming) {
297                 filter_call_dispatch (filter, call);
298         } else {
299                 g_signal_connect (call, "notify::contact",
300                                   G_CALLBACK (filter_call_contact_notify_cb),
301                                   filter);      
302         }
303 }
304 #endif
305
306 static void
307 filter_contact_list_subscribe (EmpathyFilter *filter,
308                                gpointer       user_data)
309 {
310         EmpathyContact *contact = EMPATHY_CONTACT (user_data);
311
312         empathy_subscription_dialog_show (contact, NULL);
313         g_object_unref (contact);
314 }
315
316 static void
317 filter_contact_list_local_pending_cb (EmpathyTpGroup *group,
318                                       EmpathyContact *contact,
319                                       EmpathyContact *actor,
320                                       guint           reason,
321                                       gchar          *message,
322                                       EmpathyFilter  *filter)
323 {
324         GString *str;
325
326         empathy_debug (DEBUG_DOMAIN, "New local pending contact");
327
328         empathy_contact_run_until_ready (contact,
329                                          EMPATHY_CONTACT_READY_NAME,
330                                          NULL);
331
332         str = g_string_new (NULL);
333         g_string_printf (str, _("Subscription requested by %s"),
334                          empathy_contact_get_name (contact));   
335         if (!G_STR_EMPTY (message)) {
336                 g_string_append_printf (str, _("\nMessage: %s"), message);
337         }
338
339         filter_emit_event (filter, GTK_STOCK_DIALOG_QUESTION, str->str,
340                            filter_contact_list_subscribe,
341                            g_object_ref (contact));
342
343         g_string_free (str, TRUE);
344 }
345
346 static void
347 filter_contact_list_ready_cb (EmpathyTpGroup *group,
348                               gpointer        unused,
349                               EmpathyFilter  *filter)
350 {
351         GList *pendings, *l;
352
353         if (tp_strdiff ("publish", empathy_tp_group_get_name (group))) {
354                 g_object_unref (group);
355                 return;
356         }
357
358         empathy_debug (DEBUG_DOMAIN, "Publish contact list ready");
359
360         g_signal_connect (group, "local-pending",
361                           G_CALLBACK (filter_contact_list_local_pending_cb),
362                           filter);
363
364         pendings = empathy_tp_group_get_local_pendings (group);
365         for (l = pendings; l; l = l->next) {
366                 EmpathyPendingInfo *info = l->data;
367
368                 filter_contact_list_local_pending_cb (group, info->member,
369                                                       info->actor, info->reason,
370                                                       info->message, filter);
371                 empathy_pending_info_free (info);
372         }
373         g_list_free (pendings);
374 }
375
376 static void
377 filter_tubes_async_cb (TpProxy      *channel,
378                        const GError *error,
379                        gpointer      user_data,
380                        GObject      *filter)
381 {
382         if (error) {
383                 empathy_debug (DEBUG_DOMAIN, "Error %s: %s",
384                                user_data, error->message);
385         }
386 }
387
388 typedef struct {
389         EmpathyContactFactory *factory;
390         EmpathyContact        *initiator;
391         TpChannel             *channel;
392         gchar                 *service;
393         guint                  type;
394         guint                  id;
395 } FilterTubesData;
396
397 static void
398 filter_tubes_dispatch (EmpathyFilter *filter,
399                        gpointer       user_data)
400 {
401         static TpDBusDaemon *daemon = NULL;
402         FilterTubesData     *data = user_data;
403         gchar               *thandler_bus_name;
404         gchar               *thandler_object_path;
405         gboolean             activatable = FALSE;
406         gchar              **names = NULL;
407         GtkWidget           *dialog;
408         GtkButtonsType       buttons_type;
409         gchar               *str;
410         gint                 res;
411         GError              *error = NULL;
412
413         /* Build the bus-name and object-path where the handler for this tube
414          * is supposed to be. */
415         thandler_bus_name = empathy_tube_handler_build_bus_name (data->type,
416                                                                  data->service);
417         thandler_object_path = empathy_tube_handler_build_object_path (data->type,
418                                                                        data->service);
419
420         /* Check if that bus-name is activatable, if not that means the
421          * application needed to handle this tube isn't installed. */
422         if (!daemon) {
423                 daemon = tp_dbus_daemon_new (tp_get_bus ());
424         }
425
426         if (!tp_cli_dbus_daemon_run_list_activatable_names (daemon, -1,
427                                                             &names, &error,
428                                                             NULL)) {
429                 empathy_debug (DEBUG_DOMAIN, "Error listing activatable names: %s",
430                                error->message);
431                 g_clear_error (&error);
432         } else {
433                 gchar **name;
434
435                 for (name = names; *name; name++) {
436                         if (!tp_strdiff (*name, thandler_bus_name)) {
437                                 activatable = TRUE;
438                                 break;
439                         }
440                 }
441                 g_strfreev (names);
442         }
443
444         /* Ask confirmation to the user */
445         if (activatable) {
446                 buttons_type = GTK_BUTTONS_YES_NO;
447                 str = g_strdup_printf (_("Accept invitation to play %s from %s?"),
448                                        data->service,
449                                        empathy_contact_get_name (data->initiator));
450         } else {
451                 buttons_type = GTK_BUTTONS_OK;
452                 str = g_strdup_printf (_("%s invited you to play %s but you don't "
453                                          "have it installed."),
454                                        empathy_contact_get_name (data->initiator),
455                                        data->service);
456         }
457         dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
458                                          GTK_MESSAGE_QUESTION,
459                                          buttons_type,
460                                          str);
461         g_free (str);
462         str = g_strdup_printf (_("%s Invitation"), data->service);
463         gtk_window_set_title (GTK_WINDOW (dialog), str);
464         g_free (str);
465
466         res = gtk_dialog_run (GTK_DIALOG (dialog));
467         gtk_widget_destroy (dialog);
468
469         /* Dispatch the tube if accepted by the user */
470         if (res == GTK_RESPONSE_YES) {
471                 TpProxy *connection;
472                 TpProxy *thandler;
473                 gchar   *object_path;
474                 guint    handle_type;
475                 guint    handle;
476
477                 empathy_debug (DEBUG_DOMAIN, "Tube accepted, dispatching");
478
479                 /* Create the proxy for the tube handler */
480                 thandler = g_object_new (TP_TYPE_PROXY,
481                                          "dbus-connection", tp_get_bus (),
482                                          "bus-name", thandler_bus_name,
483                                          "object-path", thandler_object_path,
484                                          NULL);
485                 tp_proxy_add_interface_by_id (thandler, EMP_IFACE_QUARK_TUBE_HANDLER);
486
487                 /* Give the tube to the handler */
488                 g_object_get (data->channel,
489                               "connection", &connection,
490                               "object-path", &object_path,
491                               "handle_type", &handle_type,
492                               "handle", &handle,
493                               NULL);
494
495                 emp_cli_tube_handler_call_handle_tube (thandler, -1,
496                                                        connection->bus_name,
497                                                        connection->object_path,
498                                                        object_path, handle_type,
499                                                        handle, data->id,
500                                                        filter_tubes_async_cb,
501                                                        "handling tube", NULL,
502                                                        G_OBJECT (filter));
503
504                 g_object_unref (thandler);
505                 g_object_unref (connection);
506                 g_free (object_path);
507         } else {
508                 empathy_debug (DEBUG_DOMAIN, "Tube rejected, closing");
509                 tp_cli_channel_type_tubes_call_close_tube (data->channel, -1,
510                                                            data->id,
511                                                            NULL, NULL, NULL,
512                                                            NULL);
513         }
514
515         g_free (thandler_bus_name);
516         g_free (thandler_object_path);
517         g_free (data->service);
518         g_object_unref (data->channel);
519         g_object_unref (data->initiator);
520         g_object_unref (data->factory);
521         g_slice_free (FilterTubesData, data);
522 }
523
524 static void
525 filter_tubes_new_tube_cb (TpChannel   *channel,
526                           guint        id,
527                           guint        initiator,
528                           guint        type,
529                           const gchar *service,
530                           GHashTable  *parameters,
531                           guint        state,
532                           gpointer     user_data,
533                           GObject     *filter)
534 {
535         EmpathyFilterPriv *priv = GET_PRIV (filter);
536         FilterTubesData   *data;
537         McAccount         *account;
538         guint              number;
539         gchar             *msg;
540
541         /* Increase tube count */
542         number = GPOINTER_TO_UINT (g_hash_table_lookup (priv->tubes, channel));
543         g_hash_table_replace (priv->tubes, g_object_ref (channel),
544                               GUINT_TO_POINTER (++number));
545         empathy_debug (DEBUG_DOMAIN, "Increased tube count for channel %p: %d",
546                        channel, number);
547
548         /* We dispatch only local pending tubes */
549         if (state != TP_TUBE_STATE_LOCAL_PENDING) {
550                 return;
551         }
552
553         account = empathy_channel_get_account (channel);
554         data = g_slice_new (FilterTubesData);
555         data->type = type;
556         data->id = id;
557         data->service = g_strdup (service);
558         data->channel = g_object_ref (channel);
559         data->factory = empathy_contact_factory_new ();
560         data->initiator = empathy_contact_factory_get_from_handle (data->factory,
561                                                                    account,
562                                                                    initiator);
563
564         empathy_contact_run_until_ready (data->initiator,
565                                          EMPATHY_CONTACT_READY_NAME, NULL);
566
567         msg = g_strdup_printf (_("%s is offering you a tube for application %s"),
568                                empathy_contact_get_name (data->initiator),
569                                service);
570
571         filter_emit_event (EMPATHY_FILTER (filter), GTK_STOCK_DIALOG_QUESTION,
572                            msg, filter_tubes_dispatch, data);
573
574         g_free (msg);
575         g_object_unref (account);
576 }
577
578 static void
579 filter_tubes_list_tubes_cb (TpChannel       *channel,
580                             const GPtrArray *tubes,
581                             const GError    *error,
582                             gpointer         user_data,
583                             GObject         *filter)
584 {
585         guint i;
586
587         if (error) {
588                 empathy_debug (DEBUG_DOMAIN, "Error listing tubes: %s",
589                                error->message);
590                 return;
591         }
592
593         for (i = 0; i < tubes->len; i++) {
594                 GValueArray *values;
595
596                 values = g_ptr_array_index (tubes, i);
597                 filter_tubes_new_tube_cb (channel,
598                                           g_value_get_uint (g_value_array_get_nth (values, 0)),
599                                           g_value_get_uint (g_value_array_get_nth (values, 1)),
600                                           g_value_get_uint (g_value_array_get_nth (values, 2)),
601                                           g_value_get_string (g_value_array_get_nth (values, 3)),
602                                           g_value_get_boxed (g_value_array_get_nth (values, 4)),
603                                           g_value_get_uint (g_value_array_get_nth (values, 5)),
604                                           user_data, filter);
605         }
606 }
607
608 static void
609 filter_tubes_channel_invalidated_cb (TpProxy       *proxy,
610                                      guint          domain,
611                                      gint           code,
612                                      gchar         *message,
613                                      EmpathyFilter *filter)
614 {
615         EmpathyFilterPriv *priv = GET_PRIV (filter);
616
617         empathy_debug (DEBUG_DOMAIN, "Channel %p invalidated: %s", proxy, message);
618
619         g_hash_table_remove (priv->tubes, proxy);
620 }
621
622 static void
623 filter_tubes_tube_closed_cb (TpChannel *channel,
624                              guint      id,
625                              gpointer   user_data,
626                              GObject   *filter)
627 {
628         EmpathyFilterPriv *priv = GET_PRIV (filter);
629         guint              number;
630
631         number = GPOINTER_TO_UINT (g_hash_table_lookup (priv->tubes, channel));
632         if (number == 1) {
633                 empathy_debug (DEBUG_DOMAIN, "Ended tube count for channel %p, "
634                                "closing channel", channel);
635                 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
636         }
637         else if (number > 1) {
638                 empathy_debug (DEBUG_DOMAIN, "Decrease tube count for channel %p: %d",
639                                channel, number);
640                 g_hash_table_replace (priv->tubes, g_object_ref (channel),
641                                       GUINT_TO_POINTER (--number));
642         }
643 }
644
645 static void
646 filter_tubes_handle_channel (EmpathyFilter *filter,
647                              TpChannel     *channel,
648                              gboolean       is_incoming)
649 {
650         EmpathyFilterPriv *priv = GET_PRIV (filter);
651
652         if (g_hash_table_lookup (priv->tubes, channel)) {
653                 return;
654         }
655
656         empathy_debug (DEBUG_DOMAIN, "Handling new channel");
657
658         g_hash_table_insert (priv->tubes, g_object_ref (channel),
659                              GUINT_TO_POINTER (0));
660
661         g_signal_connect (channel, "invalidated",
662                           G_CALLBACK (filter_tubes_channel_invalidated_cb),
663                           filter);
664
665         tp_cli_channel_type_tubes_connect_to_tube_closed (channel,
666                                                           filter_tubes_tube_closed_cb,
667                                                           NULL, NULL,
668                                                           G_OBJECT (filter), NULL);
669         tp_cli_channel_type_tubes_connect_to_new_tube (channel,
670                                                        filter_tubes_new_tube_cb,
671                                                        NULL, NULL,
672                                                        G_OBJECT (filter), NULL);
673         tp_cli_channel_type_tubes_call_list_tubes (channel, -1,
674                                                    filter_tubes_list_tubes_cb,
675                                                    NULL, NULL,
676                                                    G_OBJECT (filter));
677 }
678
679 static void
680 filter_contact_list_destroy_cb (EmpathyTpGroup *group,
681                                 EmpathyFilter  *filter)
682 {
683         g_object_unref (group);
684 }
685
686 static void
687 filter_contact_list_handle_channel (EmpathyFilter *filter,
688                                     TpChannel     *channel,
689                                     gboolean       is_incoming)
690 {
691         EmpathyTpGroup *group;
692
693         group = empathy_tp_group_new (channel);
694         g_signal_connect (group, "notify::ready",
695                           G_CALLBACK (filter_contact_list_ready_cb),
696                           filter);      
697         g_signal_connect (group, "destroy",
698                           G_CALLBACK (filter_contact_list_destroy_cb),
699                           filter);
700 }
701
702 static void
703 filter_connection_invalidated_cb (TpConnection  *connection,
704                                   guint          domain,
705                                   gint           code,
706                                   gchar         *message,
707                                   EmpathyFilter *filter)
708 {
709         EmpathyFilterPriv *priv = GET_PRIV (filter);
710         GHashTableIter     iter;
711         gpointer           key, value;
712
713         empathy_debug (DEBUG_DOMAIN, "connection invalidated: %s", message);
714
715         g_hash_table_iter_init (&iter, priv->accounts);
716         while (g_hash_table_iter_next (&iter, &key, &value)) {
717                 if (value == connection) {
718                         g_hash_table_remove (priv->accounts, key);
719                         break;
720                 }
721         }
722 }
723
724 typedef void (*HandleChannelFunc) (EmpathyFilter *filter,
725                                    TpChannel     *channel,
726                                    gboolean       is_incoming);
727
728 static void
729 filter_conection_new_channel_cb (TpConnection *connection,
730                                  const gchar  *object_path,
731                                  const gchar  *channel_type,
732                                  guint         handle_type,
733                                  guint         handle,
734                                  gboolean      suppress_handler,
735                                  gpointer      user_data,
736                                  GObject      *filter)
737 {
738         HandleChannelFunc  func = NULL;
739         TpChannel         *channel;
740         gpointer           had_channels;
741         
742         had_channels = g_object_get_data (G_OBJECT (connection), "had-channels");
743         if (had_channels == NULL) {
744                 return;
745         }
746
747         if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)) {
748                 func = filter_chat_handle_channel;
749         }
750 #ifdef HAVE_VOIP
751         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) {
752                 func = filter_call_handle_channel;
753         }
754 #endif
755         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) {
756                 func = filter_contact_list_handle_channel;
757         }
758         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES)) {
759                 func = filter_tubes_handle_channel;
760         } else {
761                 empathy_debug (DEBUG_DOMAIN, "Unknown channel type %s",
762                                channel_type);
763                 return;
764         }
765
766         channel = tp_channel_new (connection, object_path, channel_type,
767                                   handle_type, handle, NULL);
768         tp_channel_run_until_ready (channel, NULL, NULL);
769
770         /* We abuse of suppress_handler, TRUE means OUTGOING */
771         func (EMPATHY_FILTER (filter), channel, suppress_handler);
772
773         g_object_unref (channel);
774 }
775
776 static void
777 filter_connection_list_channels_cb (TpConnection    *connection,
778                                     const GPtrArray *channels,
779                                     const GError    *error,
780                                     gpointer         user_data,
781                                     GObject         *filter)
782 {
783         guint i;
784
785         g_object_set_data (G_OBJECT (connection), "had-channels",
786                            GUINT_TO_POINTER (1));
787
788         for (i = 0; i < channels->len; i++) {
789                 GValueArray *values;
790
791                 values = g_ptr_array_index (channels, i);
792                 filter_conection_new_channel_cb (connection,
793                         g_value_get_boxed (g_value_array_get_nth (values, 0)),
794                         g_value_get_string (g_value_array_get_nth (values, 1)),
795                         g_value_get_uint (g_value_array_get_nth (values, 2)),
796                         g_value_get_uint (g_value_array_get_nth (values, 3)),
797                         TRUE, user_data, filter);
798         }
799 }
800
801 #ifdef HAVE_VOIP
802 static void
803 filter_connection_advertise_capabilities_cb (TpConnection    *connection,
804                                              const GPtrArray *capabilities,
805                                              const GError    *error,
806                                              gpointer         user_data,
807                                              GObject         *filter)
808 {
809         if (error) {
810                 empathy_debug (DEBUG_DOMAIN, "Error advertising capabilities: %s",
811                                error->message);
812         }
813 }
814 #endif
815
816 static void
817 filter_connection_ready_cb (TpConnection  *connection,
818                             gpointer       unused,
819                             EmpathyFilter *filter)
820 {
821 #ifdef HAVE_VOIP
822         GPtrArray   *capabilities;
823         GType        cap_type;
824         GValue       cap = {0, };
825         const gchar *remove = NULL;
826 #endif
827
828         empathy_debug (DEBUG_DOMAIN, "Connection ready, accepting new channels");
829
830         tp_cli_connection_connect_to_new_channel (connection,
831                                                   filter_conection_new_channel_cb,
832                                                   NULL, NULL,
833                                                   G_OBJECT (filter), NULL);
834         tp_cli_connection_call_list_channels (connection, -1,
835                                               filter_connection_list_channels_cb,
836                                               NULL, NULL,
837                                               G_OBJECT (filter));
838
839 #ifdef HAVE_VOIP
840         /* Advertise VoIP capabilities */
841         capabilities = g_ptr_array_sized_new (1);
842         cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING,
843                                            G_TYPE_UINT, G_TYPE_INVALID);
844         g_value_init (&cap, cap_type);
845         g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type));
846         dbus_g_type_struct_set (&cap,
847                                 0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
848                                 1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
849                                    TP_CHANNEL_MEDIA_CAPABILITY_VIDEO |
850                                    TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN  |
851                                    TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P,
852                                 G_MAXUINT);
853         g_ptr_array_add (capabilities, g_value_get_boxed (&cap));
854
855         tp_cli_connection_interface_capabilities_call_advertise_capabilities (
856                 connection, -1,
857                 capabilities, &remove,
858                 filter_connection_advertise_capabilities_cb,
859                 NULL, NULL, G_OBJECT (filter));
860 #endif
861 }
862
863 static void
864 filter_update_account (EmpathyFilter *filter,
865                        McAccount     *account)
866 {
867         EmpathyFilterPriv *priv = GET_PRIV (filter);
868         TpConnection      *connection;
869         gboolean           ready;
870
871         connection = g_hash_table_lookup (priv->accounts, account);
872         if (connection) {
873                 return;
874         }
875
876         connection = mission_control_get_tpconnection (priv->mc, account, NULL);
877         if (!connection) {
878                 return;
879         }
880
881         g_hash_table_insert (priv->accounts, g_object_ref (account), connection);
882         g_signal_connect (connection, "invalidated",
883                           G_CALLBACK (filter_connection_invalidated_cb),
884                           filter);
885
886         g_object_get (connection, "connection-ready", &ready, NULL);
887         if (ready) {
888                 filter_connection_ready_cb (connection, NULL, filter);
889         } else {
890                 g_signal_connect (connection, "notify::connection-ready",
891                                   G_CALLBACK (filter_connection_ready_cb),
892                                   filter);
893         }
894 }
895
896 static void
897 filter_status_changed_cb (MissionControl           *mc,
898                           TpConnectionStatus        status,
899                           McPresence                presence,
900                           TpConnectionStatusReason  reason,
901                           const gchar              *unique_name,
902                           EmpathyFilter            *filter)
903 {
904         McAccount *account;
905
906         account = mc_account_lookup (unique_name);
907         filter_update_account (filter, account);
908         g_object_unref (account);
909 }
910
911 static void
912 filter_finalize (GObject *object)
913 {
914         EmpathyFilterPriv *priv = GET_PRIV (object);
915
916         empathy_disconnect_account_status_changed (priv->token);
917         g_object_unref (priv->mc);
918
919         g_slist_foreach (priv->events, (GFunc) filter_event_free, NULL);
920         g_slist_free (priv->events);
921
922         g_hash_table_destroy (priv->accounts);
923         g_hash_table_destroy (priv->tubes);
924 }
925
926 static void
927 filter_get_property (GObject    *object,
928                      guint       param_id,
929                      GValue     *value,
930                      GParamSpec *pspec)
931 {
932         EmpathyFilterPriv *priv = GET_PRIV (object);
933
934         switch (param_id) {
935         case PROP_TOP_EVENT:
936                 g_value_set_pointer (value, priv->events ? priv->events->data : NULL);
937                 break;
938         default:
939                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
940                 break;
941         };
942 }
943
944 static void
945 empathy_filter_class_init (EmpathyFilterClass *klass)
946 {
947         GObjectClass *object_class = G_OBJECT_CLASS (klass);
948
949         object_class->finalize = filter_finalize;
950         object_class->get_property = filter_get_property;
951
952         g_object_class_install_property (object_class,
953                                          PROP_TOP_EVENT,
954                                          g_param_spec_pointer ("top-event",
955                                                                "The top event",
956                                                                "The first event in the events list",
957                                                                G_PARAM_READABLE));
958
959         g_type_class_add_private (object_class, sizeof (EmpathyFilterPriv));
960 }
961
962 static void
963 empathy_filter_init (EmpathyFilter *filter)
964 {
965         EmpathyFilterPriv *priv = GET_PRIV (filter);
966         GList             *accounts, *l;
967
968         priv->tubes = g_hash_table_new_full (filter_channel_hash,
969                                              filter_channel_equal,
970                                              g_object_unref, NULL);
971
972         priv->mc = empathy_mission_control_new ();
973         priv->token = empathy_connect_to_account_status_changed (priv->mc,
974                 G_CALLBACK (filter_status_changed_cb),
975                 filter, NULL);
976
977         priv->accounts = g_hash_table_new_full (empathy_account_hash,
978                                                 empathy_account_equal,
979                                                 g_object_unref,
980                                                 g_object_unref);
981         accounts = mc_accounts_list_by_enabled (TRUE);
982         for (l = accounts; l; l = l->next) {
983                 filter_update_account (filter, l->data);
984                 g_object_unref (l->data);
985         }
986         g_list_free (accounts);
987 }
988
989 EmpathyFilter *
990 empathy_filter_new (void)
991 {
992         static EmpathyFilter *filter = NULL;
993
994         if (!filter) {
995                 filter = g_object_new (EMPATHY_TYPE_FILTER, NULL);
996                 g_object_add_weak_pointer (G_OBJECT (filter), (gpointer) &filter);
997         } else {
998                 g_object_ref (filter);
999         }
1000
1001         return filter;
1002 }
1003