]> git.0d.be Git - empathy.git/blob - src/empathy-filter.c
Quit the tubes chandler when there is no more handled channels
[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
33 #include <libmissioncontrol/mission-control.h>
34 #include <libmissioncontrol/mc-account.h>
35
36 #include <libempathy/empathy-tp-chat.h>
37 #include <libempathy/empathy-tp-call.h>
38 #include <libempathy/empathy-tp-group.h>
39 #include <libempathy/empathy-utils.h>
40 #include <libempathy/empathy-debug.h>
41
42 #include <libempathy-gtk/empathy-chat.h>
43 #include <libempathy-gtk/empathy-images.h>
44 #include <libempathy-gtk/empathy-contact-dialogs.h>
45
46 #include "empathy-filter.h"
47 #include "empathy-chat-window.h"
48 #include "empathy-call-window.h"
49
50 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
51                        EMPATHY_TYPE_FILTER, EmpathyFilterPriv))
52
53 #define DEBUG_DOMAIN "Filter"
54
55 struct _EmpathyFilterPriv {
56         GSList         *events;
57         GHashTable     *accounts;
58         gpointer        token;
59         MissionControl *mc;
60 };
61
62 static void empathy_filter_class_init (EmpathyFilterClass *klass);
63 static void empathy_filter_init       (EmpathyFilter      *filter);
64
65 G_DEFINE_TYPE (EmpathyFilter, empathy_filter, G_TYPE_OBJECT);
66
67 enum {
68         PROP_0,
69         PROP_TOP_EVENT,
70 };
71
72 typedef void (*FilterFunc) (EmpathyFilter *filter,
73                             gpointer       user_data);
74
75 typedef struct {
76         EmpathyFilterEvent public;
77         FilterFunc         func;
78         gpointer           user_data;
79 } EmpathyFilterEventExt;
80
81 static void
82 filter_event_free (EmpathyFilterEventExt *event)
83 {
84         g_free (event->public.icon_name);
85         g_free (event->public.message);
86         g_slice_free (EmpathyFilterEventExt, event);
87 }
88
89 static void
90 filter_emit_event (EmpathyFilter *filter,
91                    const gchar   *icon_name,
92                    const gchar   *message,
93                    FilterFunc     func,
94                    gpointer       user_data)
95 {
96         EmpathyFilterPriv     *priv = GET_PRIV (filter);
97         EmpathyFilterEventExt *event;
98
99         empathy_debug (DEBUG_DOMAIN, "Emit event, icon_name=%s message='%s'",
100                        icon_name, message);
101
102         event = g_slice_new0 (EmpathyFilterEventExt);
103         event->func = func;
104         event->user_data = user_data;
105         event->public.icon_name = g_strdup (icon_name);
106         event->public.message = g_strdup (message);
107
108         priv->events = g_slist_append (priv->events, event);
109         if (priv->events->data == event) {
110                 g_object_notify (G_OBJECT (filter), "top-event");
111         }
112 }
113
114 void
115 empathy_filter_activate_event (EmpathyFilter      *filter,
116                                EmpathyFilterEvent *event)
117 {
118         EmpathyFilterPriv     *priv = GET_PRIV (filter);
119         EmpathyFilterEventExt *event_ext;
120         GSList                *l;
121         gboolean               is_top;
122
123         g_return_if_fail (EMPATHY_IS_FILTER (filter));
124         g_return_if_fail (event != NULL);
125
126         if (!(l = g_slist_find (priv->events, event))) {
127                 return;
128         }
129
130         empathy_debug (DEBUG_DOMAIN, "Activating event");
131
132         event_ext = (EmpathyFilterEventExt*) event;
133         if (event_ext->func) {
134                 event_ext->func (filter, event_ext->user_data);
135         }
136
137         is_top = (l == priv->events);
138         priv->events = g_slist_delete_link (priv->events, l);
139         if (is_top) {
140                 g_object_notify (G_OBJECT (filter), "top-event");
141         }
142
143         filter_event_free (event_ext);
144 }
145
146 EmpathyFilterEvent *
147 empathy_filter_get_top_event (EmpathyFilter *filter)
148 {
149         EmpathyFilterPriv *priv = GET_PRIV (filter);
150
151         g_return_val_if_fail (EMPATHY_IS_FILTER (filter), NULL);
152
153         return priv->events ? priv->events->data : NULL;
154 }
155
156 static void
157 filter_chat_dispatch (EmpathyFilter *filter,
158                       gpointer       user_data)
159 {
160         EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (user_data);
161         McAccount     *account;
162         EmpathyChat   *chat;
163         const gchar   *id;
164
165         id = empathy_tp_chat_get_id (tp_chat);
166         account = empathy_tp_chat_get_account (tp_chat);
167         chat = empathy_chat_window_find_chat (account, id);
168
169         if (chat) {
170                 empathy_chat_set_tp_chat (chat, tp_chat);
171         } else {
172                 chat = empathy_chat_new (tp_chat);
173         }
174
175         empathy_chat_window_present_chat (chat);
176         g_object_unref (tp_chat);
177 }
178
179 static void
180 filter_chat_message_received_cb (EmpathyTpChat   *tp_chat,
181                                  EmpathyMessage  *message,
182                                  EmpathyFilter   *filter)
183 {
184         EmpathyContact  *sender;
185         gchar           *msg;
186
187         g_signal_handlers_disconnect_by_func (tp_chat,
188                                               filter_chat_message_received_cb,
189                                               filter);
190
191         sender = empathy_message_get_sender (message);
192         msg = g_strdup_printf (_("New message from %s:\n%s"),
193                                empathy_contact_get_name (sender),
194                                empathy_message_get_body (message));
195
196         filter_emit_event (filter, EMPATHY_IMAGE_NEW_MESSAGE, msg,
197                            filter_chat_dispatch, tp_chat);
198
199         g_free (msg);
200 }
201
202 static void
203 filter_chat_handle_channel (EmpathyFilter *filter,
204                             TpChannel     *channel,
205                             gboolean       is_incoming)
206 {
207         EmpathyTpChat *tp_chat;
208
209         empathy_debug (DEBUG_DOMAIN, "New text channel to be filtered: %p",
210                        channel);
211
212         tp_chat = empathy_tp_chat_new (channel, FALSE);
213         if (is_incoming) {
214                 filter_chat_dispatch (filter, tp_chat);
215         } else {
216                 g_signal_connect (tp_chat, "message-received",
217                                   G_CALLBACK (filter_chat_message_received_cb),
218                                   filter);
219         }
220 }
221
222 static void
223 filter_call_dispatch (EmpathyFilter *filter,
224                       gpointer       user_data)
225 {
226         EmpathyTpCall *call = EMPATHY_TP_CALL (user_data);
227
228         empathy_call_window_new (call);
229         g_object_unref (call);
230 }
231
232 static void
233 filter_call_contact_notify_cb (EmpathyTpCall *call,
234                                gpointer       unused,
235                                EmpathyFilter *filter)
236 {
237         EmpathyContact *contact;
238         gchar          *msg;
239
240         g_object_get (call, "contact", &contact, NULL);
241         if (!contact) {
242                 return;
243         }
244
245         empathy_contact_run_until_ready (contact,
246                                          EMPATHY_CONTACT_READY_NAME,
247                                          NULL);
248
249         msg = g_strdup_printf (_("Incoming call from %s"),
250                                empathy_contact_get_name (contact));
251
252         filter_emit_event (filter, EMPATHY_IMAGE_VOIP, msg,
253                            filter_call_dispatch, call);
254
255         g_free (msg);
256         g_object_unref (contact);
257 }
258
259 static void
260 filter_call_handle_channel (EmpathyFilter *filter,
261                             TpChannel     *channel,
262                             gboolean       is_incoming)
263 {
264         EmpathyTpCall *call;
265
266         empathy_debug (DEBUG_DOMAIN, "New media channel to be filtered: %p",
267                        channel);
268
269         call = empathy_tp_call_new (channel);
270         if (is_incoming) {
271                 filter_call_dispatch (filter, call);
272         } else {
273                 g_signal_connect (call, "notify::contact",
274                                   G_CALLBACK (filter_call_contact_notify_cb),
275                                   filter);      
276         }
277 }
278
279 static void
280 filter_contact_list_subscribe (EmpathyFilter *filter,
281                                gpointer       user_data)
282 {
283         EmpathyContact *contact = EMPATHY_CONTACT (user_data);
284
285         empathy_subscription_dialog_show (contact, NULL);
286         g_object_unref (contact);
287 }
288
289 static void
290 filter_contact_list_local_pending_cb (EmpathyTpGroup *group,
291                                       EmpathyContact *contact,
292                                       EmpathyContact *actor,
293                                       guint           reason,
294                                       gchar          *message,
295                                       EmpathyFilter  *filter)
296 {
297         GString *str;
298
299         empathy_debug (DEBUG_DOMAIN, "New local pending contact");
300
301         empathy_contact_run_until_ready (contact,
302                                          EMPATHY_CONTACT_READY_NAME,
303                                          NULL);
304
305         str = g_string_new (NULL);
306         g_string_printf (str, _("Subscription requested by %s"),
307                          empathy_contact_get_name (contact));   
308         if (!G_STR_EMPTY (message)) {
309                 g_string_append_printf (str, _("\nMessage: %s"), message);
310         }
311
312         filter_emit_event (filter, GTK_STOCK_DIALOG_QUESTION, str->str,
313                            filter_contact_list_subscribe,
314                            g_object_ref (contact));
315
316         g_string_free (str, TRUE);
317 }
318
319 static void
320 filter_contact_list_ready_cb (EmpathyTpGroup *group,
321                               gpointer        unused,
322                               EmpathyFilter  *filter)
323 {
324         GList *pendings, *l;
325
326         if (tp_strdiff ("publish", empathy_tp_group_get_name (group))) {
327                 g_object_unref (group);
328                 return;
329         }
330
331         empathy_debug (DEBUG_DOMAIN, "Publish contact list ready");
332
333         g_signal_connect (group, "local-pending",
334                           G_CALLBACK (filter_contact_list_local_pending_cb),
335                           filter);
336
337         pendings = empathy_tp_group_get_local_pendings (group);
338         for (l = pendings; l; l = l->next) {
339                 EmpathyPendingInfo *info = l->data;
340
341                 filter_contact_list_local_pending_cb (group, info->member,
342                                                       info->actor, info->reason,
343                                                       info->message, filter);
344                 empathy_pending_info_free (info);
345         }
346         g_list_free (pendings);
347 }
348
349 static void
350 filter_contact_list_destroy_cb (EmpathyTpGroup *group,
351                                 EmpathyFilter  *filter)
352 {
353         g_object_unref (group);
354 }
355
356 static void
357 filter_contact_list_handle_channel (EmpathyFilter *filter,
358                                     TpChannel     *channel,
359                                     gboolean       is_incoming)
360 {
361         EmpathyTpGroup *group;
362
363         group = empathy_tp_group_new (channel);
364         g_signal_connect (group, "notify::ready",
365                           G_CALLBACK (filter_contact_list_ready_cb),
366                           filter);      
367         g_signal_connect (group, "destroy",
368                           G_CALLBACK (filter_contact_list_destroy_cb),
369                           filter);
370 }
371
372 static void
373 filter_connection_invalidated_cb (TpConnection  *connection,
374                                   guint          domain,
375                                   gint           code,
376                                   gchar         *message,
377                                   EmpathyFilter *filter)
378 {
379         EmpathyFilterPriv *priv = GET_PRIV (filter);
380         GHashTableIter     iter;
381         gpointer           key, value;
382
383         empathy_debug (DEBUG_DOMAIN, "connection invalidated: %s", message);
384
385         g_hash_table_iter_init (&iter, priv->accounts);
386         while (g_hash_table_iter_next (&iter, &key, &value)) {
387                 if (value == connection) {
388                         g_hash_table_remove (priv->accounts, key);
389                         break;
390                 }
391         }
392 }
393
394 typedef void (*HandleChannelFunc) (EmpathyFilter *filter,
395                                    TpChannel     *channel,
396                                    gboolean       is_incoming);
397
398 static void
399 filter_conection_new_channel_cb (TpConnection *connection,
400                                  const gchar  *object_path,
401                                  const gchar  *channel_type,
402                                  guint         handle_type,
403                                  guint         handle,
404                                  gboolean      suppress_handler,
405                                  gpointer      user_data,
406                                  GObject      *filter)
407 {
408         HandleChannelFunc  func = NULL;
409         TpChannel         *channel;
410         gpointer           had_channels;
411         
412         had_channels = g_object_get_data (G_OBJECT (connection), "had-channels");
413         if (had_channels == NULL) {
414                 return;
415         }
416
417         if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)) {
418                 func = filter_chat_handle_channel;
419         }
420         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) {
421                 func = filter_call_handle_channel;
422         }
423         else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) {
424                 func = filter_contact_list_handle_channel;
425         } else {
426                 empathy_debug (DEBUG_DOMAIN, "Unknown channel type %s",
427                                channel_type);
428                 return;
429         }
430
431         channel = tp_channel_new (connection, object_path, channel_type,
432                                   handle_type, handle, NULL);
433         tp_channel_run_until_ready (channel, NULL, NULL);
434
435         /* We abuse of suppress_handler, TRUE means OUTGOING */
436         func (EMPATHY_FILTER (filter), channel, suppress_handler);
437
438         g_object_unref (channel);
439 }
440
441 static void
442 filter_connection_list_channels_cb (TpConnection    *connection,
443                                     const GPtrArray *channels,
444                                     const GError    *error,
445                                     gpointer         user_data,
446                                     GObject         *filter)
447 {
448         guint i;
449
450         g_object_set_data (G_OBJECT (connection), "had-channels",
451                            GUINT_TO_POINTER (1));
452
453         for (i = 0; i < channels->len; i++) {
454                 GValueArray *values;
455
456                 values = g_ptr_array_index (channels, i);
457                 filter_conection_new_channel_cb (connection,
458                         g_value_get_boxed (g_value_array_get_nth (values, 0)),
459                         g_value_get_string (g_value_array_get_nth (values, 1)),
460                         g_value_get_uint (g_value_array_get_nth (values, 2)),
461                         g_value_get_uint (g_value_array_get_nth (values, 3)),
462                         TRUE, user_data, filter);
463         }
464 }
465
466 static void
467 filter_connection_ready_cb (TpConnection  *connection,
468                             gpointer       unused,
469                             EmpathyFilter *filter)
470 {
471         empathy_debug (DEBUG_DOMAIN, "Connection ready, accepting new channels");
472
473         tp_cli_connection_connect_to_new_channel (connection,
474                                                   filter_conection_new_channel_cb,
475                                                   NULL, NULL,
476                                                   G_OBJECT (filter), NULL);
477         tp_cli_connection_call_list_channels (connection, -1,
478                                               filter_connection_list_channels_cb,
479                                               NULL, NULL,
480                                               G_OBJECT (filter));
481 }
482
483 static void
484 filter_update_account (EmpathyFilter *filter,
485                        McAccount     *account)
486 {
487         EmpathyFilterPriv *priv = GET_PRIV (filter);
488         TpConnection      *connection;
489         gboolean           ready;
490
491         connection = g_hash_table_lookup (priv->accounts, account);
492         if (connection) {
493                 return;
494         }
495
496         connection = mission_control_get_tpconnection (priv->mc, account, NULL);
497         if (!connection) {
498                 return;
499         }
500
501         g_hash_table_insert (priv->accounts, g_object_ref (account), connection);
502         g_signal_connect (connection, "invalidated",
503                           G_CALLBACK (filter_connection_invalidated_cb),
504                           filter);
505
506         g_object_get (connection, "connection-ready", &ready, NULL);
507         if (ready) {
508                 filter_connection_ready_cb (connection, NULL, filter);
509         } else {
510                 g_signal_connect (connection, "notify::connection-ready",
511                                   G_CALLBACK (filter_connection_ready_cb),
512                                   filter);
513         }
514 }
515
516 static void
517 filter_status_changed_cb (MissionControl           *mc,
518                           TpConnectionStatus        status,
519                           McPresence                presence,
520                           TpConnectionStatusReason  reason,
521                           const gchar              *unique_name,
522                           EmpathyFilter            *filter)
523 {
524         McAccount *account;
525
526         account = mc_account_lookup (unique_name);
527         filter_update_account (filter, account);
528         g_object_unref (account);
529 }
530
531 static void
532 filter_finalize (GObject *object)
533 {
534         EmpathyFilterPriv *priv = GET_PRIV (object);
535
536         empathy_disconnect_account_status_changed (priv->token);
537         g_object_unref (priv->mc);
538
539         g_slist_foreach (priv->events, (GFunc) filter_event_free, NULL);
540         g_slist_free (priv->events);
541
542         g_hash_table_destroy (priv->accounts);
543 }
544
545 static void
546 filter_get_property (GObject    *object,
547                      guint       param_id,
548                      GValue     *value,
549                      GParamSpec *pspec)
550 {
551         EmpathyFilterPriv *priv = GET_PRIV (object);
552
553         switch (param_id) {
554         case PROP_TOP_EVENT:
555                 g_value_set_pointer (value, priv->events ? priv->events->data : NULL);
556                 break;
557         default:
558                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
559                 break;
560         };
561 }
562
563 static void
564 empathy_filter_class_init (EmpathyFilterClass *klass)
565 {
566         GObjectClass *object_class = G_OBJECT_CLASS (klass);
567
568         object_class->finalize = filter_finalize;
569         object_class->get_property = filter_get_property;
570
571         g_object_class_install_property (object_class,
572                                          PROP_TOP_EVENT,
573                                          g_param_spec_pointer ("top-event",
574                                                                "The top event",
575                                                                "The first event in the events list",
576                                                                G_PARAM_READABLE));
577
578         g_type_class_add_private (object_class, sizeof (EmpathyFilterPriv));
579 }
580
581 static void
582 empathy_filter_init (EmpathyFilter *filter)
583 {
584         EmpathyFilterPriv *priv = GET_PRIV (filter);
585         GList             *accounts, *l;
586
587         priv->mc = empathy_mission_control_new ();
588         priv->token = empathy_connect_to_account_status_changed (priv->mc,
589                 G_CALLBACK (filter_status_changed_cb),
590                 filter, NULL);
591
592         priv->accounts = g_hash_table_new_full (empathy_account_hash,
593                                                 empathy_account_equal,
594                                                 g_object_unref,
595                                                 g_object_unref);
596         accounts = mc_accounts_list_by_enabled (TRUE);
597         for (l = accounts; l; l = l->next) {
598                 filter_update_account (filter, l->data);
599                 g_object_unref (l->data);
600         }
601         g_list_free (accounts);
602 }
603
604 EmpathyFilter *
605 empathy_filter_new (void)
606 {
607         static EmpathyFilter *filter = NULL;
608
609         if (!filter) {
610                 filter = g_object_new (EMPATHY_TYPE_FILTER, NULL);
611                 g_object_add_weak_pointer (G_OBJECT (filter), (gpointer) &filter);
612         } else {
613                 g_object_ref (filter);
614         }
615
616         return filter;
617 }
618