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