]> git.0d.be Git - empathy.git/blob - src/empathy-event-manager.c
event-manager: remove the auto-remove timer when the event is destroyed
[empathy.git] / src / empathy-event-manager.c
1 /*
2  * Copyright (C) 2007-2008 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Xavier Claessens <xclaesse@gmail.com>
19  *          Sjoerd Simons <sjoerd.simons@collabora.co.uk>
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25 #include <glib/gi18n.h>
26
27 #include <telepathy-glib/account-manager.h>
28 #include <telepathy-glib/util.h>
29 #include <telepathy-glib/interfaces.h>
30
31 #include <libempathy/empathy-dispatcher.h>
32 #include <libempathy/empathy-idle.h>
33 #include <libempathy/empathy-tp-contact-factory.h>
34 #include <libempathy/empathy-contact-manager.h>
35 #include <libempathy/empathy-tp-chat.h>
36 #include <libempathy/empathy-tp-call.h>
37 #include <libempathy/empathy-tp-file.h>
38 #include <libempathy/empathy-utils.h>
39 #include <libempathy/empathy-call-factory.h>
40
41 #include <extensions/extensions.h>
42
43 #include <libempathy-gtk/empathy-conf.h>
44 #include <libempathy-gtk/empathy-images.h>
45 #include <libempathy-gtk/empathy-contact-dialogs.h>
46 #include <libempathy-gtk/empathy-sound.h>
47
48 #include "empathy-event-manager.h"
49 #include "empathy-main-window.h"
50 #include "empathy-tube-dispatch.h"
51
52 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
53 #include <libempathy/empathy-debug.h>
54
55 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyEventManager)
56
57 #define NOTIFICATION_TIMEOUT 2 /* seconds */
58
59 /* The time interval in milliseconds between 2 incoming rings */
60 #define MS_BETWEEN_RING 500
61
62 typedef struct {
63   EmpathyEventManager *manager;
64   EmpathyDispatchOperation *operation;
65   gulong approved_handler;
66   gulong claimed_handler;
67   gulong invalidated_handler;
68   /* Remove contact if applicable */
69   EmpathyContact *contact;
70   /* Tube dispatcher if applicable */
71   EmpathyTubeDispatch *tube_dispatch;
72   /* option signal handler and it's instance */
73   gulong handler;
74   GObject *handler_instance;
75   /* optional accept widget */
76   GtkWidget *dialog;
77 } EventManagerApproval;
78
79 typedef struct {
80   EmpathyDispatcher *dispatcher;
81   EmpathyContactManager *contact_manager;
82   GSList *events;
83   /* Ongoing approvals */
84   GSList *approvals;
85
86   gint ringing;
87 } EmpathyEventManagerPriv;
88
89 typedef struct _EventPriv EventPriv;
90 typedef void (*EventFunc) (EventPriv *event);
91
92 struct _EventPriv {
93   EmpathyEvent public;
94   EmpathyEventManager *manager;
95   EventManagerApproval *approval;
96   EventFunc func;
97   gboolean inhibit;
98   gpointer user_data;
99   guint autoremove_timeout_id;
100 };
101
102 enum {
103   EVENT_ADDED,
104   EVENT_REMOVED,
105   EVENT_UPDATED,
106   LAST_SIGNAL
107 };
108
109 static guint signals[LAST_SIGNAL];
110
111 G_DEFINE_TYPE (EmpathyEventManager, empathy_event_manager, G_TYPE_OBJECT);
112
113 static EmpathyEventManager * manager_singleton = NULL;
114
115 static EventManagerApproval *
116 event_manager_approval_new (EmpathyEventManager *manager,
117   EmpathyDispatchOperation *operation)
118 {
119   EventManagerApproval *result = g_slice_new0 (EventManagerApproval);
120   result->operation = g_object_ref (operation);
121   result->manager = manager;
122
123   return result;
124 }
125
126 static void
127 event_manager_approval_free (EventManagerApproval *approval)
128 {
129   g_signal_handler_disconnect (approval->operation,
130     approval->approved_handler);
131   g_signal_handler_disconnect (approval->operation,
132     approval->claimed_handler);
133   g_signal_handler_disconnect (approval->operation,
134     approval->invalidated_handler);
135   g_object_unref (approval->operation);
136
137   if (approval->handler != 0)
138     g_signal_handler_disconnect (approval->handler_instance,
139       approval->handler);
140
141   if (approval->contact != NULL)
142     g_object_unref (approval->contact);
143
144   if (approval->tube_dispatch != NULL)
145     g_object_unref (approval->tube_dispatch);
146
147   if (approval->dialog != NULL)
148     {
149       gtk_widget_destroy (approval->dialog);
150     }
151
152   g_slice_free (EventManagerApproval, approval);
153 }
154
155 static void event_remove (EventPriv *event);
156
157 static void
158 event_free (EventPriv *event)
159 {
160   g_free (event->public.icon_name);
161   g_free (event->public.header);
162   g_free (event->public.message);
163
164   if (event->autoremove_timeout_id != 0)
165     g_source_remove (event->autoremove_timeout_id);
166
167   if (event->public.contact)
168     {
169       g_object_unref (event->public.contact);
170     }
171
172   g_slice_free (EventPriv, event);
173 }
174
175 static void
176 event_remove (EventPriv *event)
177 {
178   EmpathyEventManagerPriv *priv = GET_PRIV (event->manager);
179
180   DEBUG ("Removing event %p", event);
181
182   priv->events = g_slist_remove (priv->events, event);
183   g_signal_emit (event->manager, signals[EVENT_REMOVED], 0, event);
184   event_free (event);
185 }
186
187 static gboolean
188 autoremove_event_timeout_cb (EventPriv *event)
189 {
190   event->autoremove_timeout_id = 0;
191   event_remove (event);
192   return FALSE;
193 }
194
195 static void
196 event_manager_add (EmpathyEventManager *manager,
197     EmpathyContact *contact,
198     EmpathyEventType type,
199     const gchar *icon_name,
200     const gchar *header,
201     const gchar *message,
202     EventManagerApproval *approval,
203     EventFunc func,
204     gpointer user_data)
205 {
206   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
207   EventPriv               *event;
208
209   event = g_slice_new0 (EventPriv);
210   event->public.contact = contact ? g_object_ref (contact) : NULL;
211   event->public.type = type;
212   event->public.icon_name = g_strdup (icon_name);
213   event->public.header = g_strdup (header);
214   event->public.message = g_strdup (message);
215   event->public.must_ack = (func != NULL);
216   event->inhibit = FALSE;
217   event->func = func;
218   event->user_data = user_data;
219   event->manager = manager;
220   event->approval = approval;
221
222   DEBUG ("Adding event %p", event);
223   priv->events = g_slist_prepend (priv->events, event);
224   g_signal_emit (event->manager, signals[EVENT_ADDED], 0, event);
225
226   if (!event->public.must_ack)
227     {
228       event->autoremove_timeout_id = g_timeout_add_seconds (
229           NOTIFICATION_TIMEOUT, (GSourceFunc) autoremove_event_timeout_cb,
230           event);
231     }
232 }
233
234 static void
235 event_channel_process_func (EventPriv *event)
236 {
237   empathy_dispatch_operation_approve (event->approval->operation);
238 }
239
240 static void
241 event_text_channel_process_func (EventPriv *event)
242 {
243   EmpathyTpChat *tp_chat;
244
245   if (event->approval->handler != 0)
246     {
247       tp_chat = EMPATHY_TP_CHAT
248         (empathy_dispatch_operation_get_channel_wrapper (event->approval->operation));
249
250       g_signal_handler_disconnect (tp_chat, event->approval->handler);
251       event->approval->handler = 0;
252     }
253
254   empathy_dispatch_operation_approve (event->approval->operation);
255 }
256
257 static EventPriv *
258 event_lookup_by_approval (EmpathyEventManager *manager,
259   EventManagerApproval *approval)
260 {
261   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
262   GSList *l;
263   EventPriv *retval = NULL;
264
265   for (l = priv->events; l; l = l->next)
266     {
267       EventPriv *event = l->data;
268
269       if (event->approval == approval)
270         {
271           retval = event;
272           break;
273         }
274     }
275
276   return retval;
277 }
278
279 static void
280 event_update (EmpathyEventManager *manager, EventPriv *event,
281   const char *icon_name, const char *header, const char *msg)
282 {
283   g_free (event->public.icon_name);
284   g_free (event->public.header);
285   g_free (event->public.message);
286
287   event->public.icon_name = g_strdup (icon_name);
288   event->public.header = g_strdup (header);
289   event->public.message = g_strdup (msg);
290
291   g_signal_emit (manager, signals[EVENT_UPDATED], 0, event);
292 }
293
294 static void
295 event_manager_call_window_confirmation_dialog_response_cb (GtkDialog *dialog,
296   gint response, gpointer user_data)
297 {
298   EventManagerApproval *approval = user_data;
299
300   gtk_widget_destroy (approval->dialog);
301   approval->dialog = NULL;
302
303   if (response != GTK_RESPONSE_ACCEPT)
304     {
305       EmpathyTpCall *call =
306         EMPATHY_TP_CALL (
307           empathy_dispatch_operation_get_channel_wrapper (
308             approval->operation));
309
310       g_object_ref (call);
311       if (empathy_dispatch_operation_claim (approval->operation))
312         empathy_tp_call_close (call);
313       g_object_unref (call);
314
315     }
316   else
317     {
318       EmpathyCallFactory *factory = empathy_call_factory_get ();
319       empathy_call_factory_claim_channel (factory, approval->operation);
320     }
321 }
322
323 static void
324 event_channel_process_voip_func (EventPriv *event)
325 {
326   GtkWidget *dialog;
327   GtkWidget *button;
328   GtkWidget *image;
329   EmpathyTpCall *call;
330   gboolean video;
331
332   if (event->approval->dialog != NULL)
333     {
334       gtk_window_present (GTK_WINDOW (event->approval->dialog));
335       return;
336     }
337
338   call = EMPATHY_TP_CALL (empathy_dispatch_operation_get_channel_wrapper (
339         event->approval->operation));
340
341   video = empathy_tp_call_has_initial_video (call);
342
343   dialog = gtk_message_dialog_new (NULL, 0,
344       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
345       video ? _("Incoming video call"): _("Incoming call"));
346
347   gtk_message_dialog_format_secondary_text (
348     GTK_MESSAGE_DIALOG (dialog), video ?
349       _("%s is video calling you, do you want to answer?"):
350       _("%s is calling you, do you want to answer?"),
351       empathy_contact_get_name (event->approval->contact));
352
353   /* Set image of the dialog */
354   if (video)
355     {
356       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
357           GTK_ICON_SIZE_DIALOG);
358     }
359   else
360     {
361       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP,
362           GTK_ICON_SIZE_DIALOG);
363     }
364
365   gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
366   gtk_widget_show (image);
367
368   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
369       GTK_RESPONSE_OK);
370
371   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
372       _("_Reject"), GTK_RESPONSE_REJECT);
373   image = gtk_image_new_from_icon_name ("call-stop",
374     GTK_ICON_SIZE_BUTTON);
375   gtk_button_set_image (GTK_BUTTON (button), image);
376
377   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
378       _("_Answer"), GTK_RESPONSE_ACCEPT);
379
380   image = gtk_image_new_from_icon_name ("call-start", GTK_ICON_SIZE_BUTTON);
381   gtk_button_set_image (GTK_BUTTON (button), image);
382
383   g_signal_connect (dialog, "response",
384       G_CALLBACK (event_manager_call_window_confirmation_dialog_response_cb),
385       event->approval);
386
387   gtk_widget_show (dialog);
388
389   event->approval->dialog = dialog;
390 }
391
392 static void
393 event_manager_chat_message_received_cb (EmpathyTpChat *tp_chat,
394   EmpathyMessage *message, EventManagerApproval *approval)
395 {
396   EmpathyContact  *sender;
397   const gchar     *header;
398   const gchar     *msg;
399   TpChannel       *channel;
400   EventPriv       *event;
401
402   /* try to update the event if it's referring to a chat which is already in the
403    * queue. */
404   event = event_lookup_by_approval (approval->manager, approval);
405
406   sender = empathy_message_get_sender (message);
407   header = empathy_contact_get_name (sender);
408   msg = empathy_message_get_body (message);
409
410   channel = empathy_tp_chat_get_channel (tp_chat);
411
412   if (event != NULL)
413     event_update (approval->manager, event, EMPATHY_IMAGE_NEW_MESSAGE, header, msg);
414   else
415     event_manager_add (approval->manager, sender, EMPATHY_EVENT_TYPE_CHAT,
416         EMPATHY_IMAGE_NEW_MESSAGE, header, msg, approval,
417         event_text_channel_process_func, NULL);
418
419   empathy_sound_play (empathy_main_window_get (),
420     EMPATHY_SOUND_CONVERSATION_NEW);
421 }
422
423 static void
424 event_manager_approval_done (EventManagerApproval *approval)
425 {
426   EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager);
427   GSList                  *l;
428
429   if (approval->operation != NULL)
430     {
431       GQuark channel_type;
432
433       channel_type = empathy_dispatch_operation_get_channel_type_id (
434           approval->operation);
435       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
436         {
437           priv->ringing--;
438           if (priv->ringing == 0)
439             empathy_sound_stop (EMPATHY_SOUND_PHONE_INCOMING);
440         }
441     }
442
443   priv->approvals = g_slist_remove (priv->approvals, approval);
444
445   for (l = priv->events; l; l = l->next)
446     {
447       EventPriv *event = l->data;
448
449       if (event->approval == approval)
450         {
451           event_remove (event);
452           break;
453         }
454     }
455
456   event_manager_approval_free (approval);
457 }
458
459 static void
460 event_manager_operation_approved_cb (EmpathyDispatchOperation *operation,
461   EventManagerApproval *approval)
462 {
463   event_manager_approval_done (approval);
464 }
465
466 static void
467 event_manager_operation_claimed_cb (EmpathyDispatchOperation *operation,
468   EventManagerApproval *approval)
469 {
470   event_manager_approval_done (approval);
471 }
472
473 static void
474 event_manager_operation_invalidated_cb (EmpathyDispatchOperation *operation,
475   guint domain, gint code, gchar *message,
476   EventManagerApproval *approval)
477 {
478   event_manager_approval_done (approval);
479 }
480
481 static void
482 event_manager_media_channel_got_contact (EventManagerApproval *approval)
483 {
484   EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager);
485   gchar *header;
486   EmpathyTpCall *call;
487   gboolean video;
488
489   call = EMPATHY_TP_CALL (empathy_dispatch_operation_get_channel_wrapper (
490         approval->operation));
491
492   video = empathy_tp_call_has_initial_video (call);
493
494   header = g_strdup_printf (
495     video ? _("Incoming video call from %s") :_("Incoming call from %s"),
496     empathy_contact_get_name (approval->contact));
497
498   event_manager_add (approval->manager, approval->contact,
499       EMPATHY_EVENT_TYPE_VOIP,
500       video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
501       header, NULL, approval,
502       event_channel_process_voip_func, NULL);
503
504   g_free (header);
505
506   priv->ringing++;
507   if (priv->ringing == 1)
508     empathy_sound_start_playing (empathy_main_window_get (),
509         EMPATHY_SOUND_PHONE_INCOMING, MS_BETWEEN_RING);
510 }
511
512 static void
513 event_manager_media_channel_contact_changed_cb (EmpathyTpCall *call,
514   GParamSpec *param, EventManagerApproval *approval)
515 {
516   EmpathyContact *contact;
517
518   g_object_get (G_OBJECT (call), "contact", &contact, NULL);
519
520   if (contact == NULL)
521     return;
522
523   approval->contact = contact;
524   event_manager_media_channel_got_contact (approval);
525 }
526
527 static void
528 event_manager_tube_approved_cb (EventPriv *event)
529 {
530   empathy_tube_dispatch_handle (event->approval->tube_dispatch);
531 }
532
533 static void
534 event_manager_add_tube_approval (EventManagerApproval *approval,
535   EmpathyTubeDispatchAbility ability)
536 {
537   const gchar *icon_name;
538   gchar       *header;
539   const gchar *msg;
540
541   header = g_strdup_printf (_("%s is offering you an invitation"),
542     empathy_contact_get_name (approval->contact));
543
544   if (ability == EMPATHY_TUBE_DISPATCHABILITY_POSSIBLE)
545     {
546       icon_name = GTK_STOCK_EXECUTE;
547       msg = _("An external application will be started to handle it.");
548     }
549   else
550     {
551       icon_name = GTK_STOCK_DIALOG_ERROR;
552       msg = _("You don't have the needed external "
553               "application to handle it.");
554     }
555
556   event_manager_add (approval->manager, approval->contact,
557       EMPATHY_EVENT_TYPE_TUBE, icon_name, header, msg, approval,
558       event_manager_tube_approved_cb, approval);
559
560   g_free (header);
561   /* FIXME better sound for incoming tubes ? */
562   empathy_sound_play (empathy_main_window_get (),
563     EMPATHY_SOUND_CONVERSATION_NEW);
564 }
565
566 static void
567 event_manager_tube_dispatch_ability_cb (GObject *object,
568    GParamSpec *spec, gpointer user_data)
569 {
570   EventManagerApproval *approval = (EventManagerApproval *) user_data;
571   EmpathyTubeDispatchAbility dispatchability;
572
573   dispatchability =
574     empathy_tube_dispatch_is_dispatchable (approval->tube_dispatch);
575
576   if (dispatchability != EMPATHY_TUBE_DISPATCHABILITY_UNKNOWN)
577     {
578       event_manager_add_tube_approval (approval, dispatchability);
579       g_signal_handler_disconnect (object, approval->handler);
580       approval->handler = 0;
581     }
582 }
583
584 static void
585 event_manager_tube_got_contact_cb (EmpathyTpContactFactory *factory,
586                                    EmpathyContact *contact,
587                                    const GError *error,
588                                    gpointer user_data,
589                                    GObject *object)
590 {
591   EventManagerApproval *approval = (EventManagerApproval *) user_data;
592   EmpathyTubeDispatchAbility dispatchability;
593
594   if (error != NULL)
595     {
596       /* FIXME: We should probably still display the event */
597       DEBUG ("Error: %s", error->message);
598       return;
599     }
600
601   approval->contact = g_object_ref (contact);
602
603   dispatchability = empathy_tube_dispatch_is_dispatchable
604     (approval->tube_dispatch);
605
606   switch (dispatchability)
607     {
608       case EMPATHY_TUBE_DISPATCHABILITY_UNKNOWN:
609         approval->handler = g_signal_connect (approval->tube_dispatch,
610           "notify::dispatchability",
611           G_CALLBACK (event_manager_tube_dispatch_ability_cb), approval);
612         approval->handler_instance = G_OBJECT (approval->tube_dispatch);
613         break;
614       case EMPATHY_TUBE_DISPATCHABILITY_POSSIBLE:
615         /* fallthrough */
616       case EMPATHY_TUBE_DISPATCHABILITY_IMPOSSIBLE:
617         event_manager_add_tube_approval (approval, dispatchability);
618         break;
619     }
620 }
621
622 static void
623 invite_dialog_response_cb (GtkDialog *dialog,
624                            gint response,
625                            EventManagerApproval *approval)
626 {
627   EmpathyTpChat *tp_chat;
628   TpChannel *channel;
629   TpHandle self_handle;
630   GArray *members;
631
632   gtk_widget_destroy (GTK_WIDGET (approval->dialog));
633   approval->dialog = NULL;
634
635   tp_chat = EMPATHY_TP_CHAT (empathy_dispatch_operation_get_channel_wrapper (
636         approval->operation));
637
638   if (response != GTK_RESPONSE_OK)
639     {
640       /* close channel */
641       DEBUG ("Muc invitation rejected");
642
643       if (empathy_dispatch_operation_claim (approval->operation))
644         empathy_tp_chat_close (tp_chat);
645       return;
646     }
647
648   DEBUG ("Muc invitation accepted");
649
650   /* join the room */
651   channel = empathy_tp_chat_get_channel (tp_chat);
652
653   self_handle = tp_channel_group_get_self_handle (channel);
654   members = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
655   g_array_append_val (members, self_handle);
656
657   tp_cli_channel_interface_group_call_add_members (channel, -1, members,
658       "", NULL, NULL, NULL, NULL);
659
660   empathy_dispatch_operation_approve (approval->operation);
661
662   g_array_free (members, TRUE);
663 }
664
665 static void
666 event_room_channel_process_func (EventPriv *event)
667 {
668   GtkWidget *dialog, *button, *image;
669   TpChannel *channel = empathy_dispatch_operation_get_channel (
670       event->approval->operation);
671
672   if (event->approval->dialog != NULL)
673     {
674       gtk_window_present (GTK_WINDOW (event->approval->dialog));
675       return;
676     }
677
678   /* create dialog */
679   dialog = gtk_message_dialog_new (NULL, 0,
680       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Room invitation"));
681
682   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
683       _("%s is inviting you to join %s"),
684       empathy_contact_get_name (event->approval->contact),
685       tp_channel_get_identifier (channel));
686
687   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
688       GTK_RESPONSE_OK);
689
690   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
691       _("_Decline"), GTK_RESPONSE_CANCEL);
692   image = gtk_image_new_from_icon_name (GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
693   gtk_button_set_image (GTK_BUTTON (button), image);
694
695   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
696       _("_Join"), GTK_RESPONSE_OK);
697   image = gtk_image_new_from_icon_name (GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON);
698   gtk_button_set_image (GTK_BUTTON (button), image);
699
700   g_signal_connect (dialog, "response",
701       G_CALLBACK (invite_dialog_response_cb), event->approval);
702
703   gtk_widget_show (dialog);
704
705   event->approval->dialog = dialog;
706 }
707
708 static void
709 event_manager_muc_invite_got_contact_cb (EmpathyTpContactFactory *factory,
710                                          EmpathyContact *contact,
711                                          const GError *error,
712                                          gpointer user_data,
713                                          GObject *object)
714 {
715   EventManagerApproval *approval = (EventManagerApproval *) user_data;
716   TpChannel *channel;
717   const gchar *invite_msg;
718   gchar *msg;
719   TpHandle self_handle;
720
721   if (error != NULL)
722     {
723       /* FIXME: We should probably still display the event */
724       DEBUG ("Error: %s", error->message);
725       return;
726     }
727
728   approval->contact = g_object_ref (contact);
729   channel = empathy_dispatch_operation_get_channel (approval->operation);
730
731   self_handle = tp_channel_group_get_self_handle (channel);
732   tp_channel_group_get_local_pending_info (channel, self_handle, NULL, NULL,
733       &invite_msg);
734
735   msg = g_strdup_printf (_("%s invited you to join %s"),
736       empathy_contact_get_name (approval->contact),
737       tp_channel_get_identifier (channel));
738
739   event_manager_add (approval->manager, approval->contact,
740       EMPATHY_EVENT_TYPE_CHAT, EMPATHY_IMAGE_GROUP_MESSAGE, msg, invite_msg,
741       approval, event_room_channel_process_func, NULL);
742
743   empathy_sound_play (empathy_main_window_get (),
744     EMPATHY_SOUND_CONVERSATION_NEW);
745
746   g_free (msg);
747 }
748
749 static void
750 event_manager_ft_got_contact_cb (EmpathyTpContactFactory *factory,
751                                  EmpathyContact *contact,
752                                  const GError *error,
753                                  gpointer user_data,
754                                  GObject *object)
755 {
756   EventManagerApproval *approval = (EventManagerApproval *) user_data;
757   char *header;
758
759   approval->contact = g_object_ref (contact);
760
761   header = g_strdup_printf (_("Incoming file transfer from %s"),
762                             empathy_contact_get_name (approval->contact));
763
764   event_manager_add (approval->manager, approval->contact,
765       EMPATHY_EVENT_TYPE_TRANSFER, EMPATHY_IMAGE_DOCUMENT_SEND, header, NULL,
766       approval, event_channel_process_func, NULL);
767
768   /* FIXME better sound for incoming file transfers ?*/
769   empathy_sound_play (empathy_main_window_get (),
770                       EMPATHY_SOUND_CONVERSATION_NEW);
771
772   g_free (header);
773 }
774
775 static void
776 event_manager_approve_channel_cb (EmpathyDispatcher *dispatcher,
777   EmpathyDispatchOperation  *operation, EmpathyEventManager *manager)
778 {
779   const gchar *channel_type;
780   EventManagerApproval *approval;
781   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
782
783   channel_type = empathy_dispatch_operation_get_channel_type (operation);
784
785   approval = event_manager_approval_new (manager, operation);
786   priv->approvals = g_slist_prepend (priv->approvals, approval);
787
788   approval->approved_handler = g_signal_connect (operation, "approved",
789     G_CALLBACK (event_manager_operation_approved_cb), approval);
790
791   approval->claimed_handler = g_signal_connect (operation, "claimed",
792      G_CALLBACK (event_manager_operation_claimed_cb), approval);
793
794   approval->invalidated_handler = g_signal_connect (operation, "invalidated",
795      G_CALLBACK (event_manager_operation_invalidated_cb), approval);
796
797   if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT))
798     {
799       EmpathyTpChat *tp_chat =
800         EMPATHY_TP_CHAT (
801           empathy_dispatch_operation_get_channel_wrapper (operation));
802       TpChannel *channel = empathy_tp_chat_get_channel (tp_chat);
803
804       if (tp_proxy_has_interface (channel, TP_IFACE_CHANNEL_INTERFACE_GROUP))
805         {
806           /* Are we in local-pending ? */
807           TpHandle self_handle, inviter;
808
809           self_handle = tp_channel_group_get_self_handle (channel);
810
811           if (self_handle != 0 && tp_channel_group_get_local_pending_info (
812                 channel, self_handle, &inviter, NULL, NULL))
813             {
814               /* We are invited to a room */
815               EmpathyTpContactFactory *factory;
816               TpConnection *connection;
817
818               DEBUG ("Have been invited to %s. Ask user if he wants to accept",
819                   tp_channel_get_identifier (channel));
820
821               connection = empathy_tp_chat_get_connection (tp_chat);
822               factory = empathy_tp_contact_factory_dup_singleton (connection);
823
824               empathy_tp_contact_factory_get_from_handle (factory,
825                   inviter, event_manager_muc_invite_got_contact_cb,
826                   approval, NULL, G_OBJECT (manager));
827
828               g_object_unref (factory);
829               return;
830             }
831
832           /* if we are not invited, let's wait for the first message */
833         }
834
835       /* 1-1 text channel, wait for the first message */
836       approval->handler = g_signal_connect (tp_chat, "message-received",
837         G_CALLBACK (event_manager_chat_message_received_cb), approval);
838       approval->handler_instance = G_OBJECT (tp_chat);
839     }
840   else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
841     {
842       EmpathyContact *contact;
843       EmpathyTpCall *call = EMPATHY_TP_CALL (
844           empathy_dispatch_operation_get_channel_wrapper (operation));
845
846       g_object_get (G_OBJECT (call), "contact", &contact, NULL);
847
848       if (contact == NULL)
849         {
850           g_signal_connect (call, "notify::contact",
851             G_CALLBACK (event_manager_media_channel_contact_changed_cb),
852             approval);
853         }
854       else
855         {
856           approval->contact = contact;
857           event_manager_media_channel_got_contact (approval);
858         }
859
860     }
861   else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
862     {
863       TpChannel *channel;
864       TpConnection *connection;
865       TpHandle handle;
866       EmpathyTpContactFactory *factory;
867
868       channel = empathy_dispatch_operation_get_channel (operation);
869       handle = tp_channel_get_handle (channel, NULL);
870
871       connection = tp_channel_borrow_connection (channel);
872       factory = empathy_tp_contact_factory_dup_singleton (connection);
873       empathy_tp_contact_factory_get_from_handle (factory, handle,
874         event_manager_ft_got_contact_cb, approval, NULL, G_OBJECT (manager));
875
876       g_object_unref (factory);
877     }
878   else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE) ||
879       !tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE))
880     {
881       TpChannel *channel;
882       TpHandle handle;
883       TpHandleType handle_type;
884       TpConnection *connection;
885       EmpathyTpContactFactory *factory;
886
887       channel = empathy_dispatch_operation_get_channel (operation);
888       handle = tp_channel_get_handle (channel, &handle_type);
889
890       /* Only understand p2p tubes */
891       if (handle_type != TP_HANDLE_TYPE_CONTACT)
892         return;
893
894       approval->tube_dispatch = empathy_tube_dispatch_new (operation);
895       connection = tp_channel_borrow_connection (channel);
896       factory = empathy_tp_contact_factory_dup_singleton (connection);
897       empathy_tp_contact_factory_get_from_handle (factory, handle,
898         event_manager_tube_got_contact_cb, approval, NULL, G_OBJECT (manager));
899     }
900   else
901     {
902       DEBUG ("Unknown channel type (%s), ignoring..", channel_type);
903     }
904 }
905
906 static void
907 event_pending_subscribe_func (EventPriv *event)
908 {
909   empathy_subscription_dialog_show (event->public.contact, NULL);
910   event_remove (event);
911 }
912
913 static void
914 event_manager_pendings_changed_cb (EmpathyContactList  *list,
915   EmpathyContact *contact, EmpathyContact *actor,
916   guint reason, gchar *message, gboolean is_pending,
917   EmpathyEventManager *manager)
918 {
919   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
920   gchar                   *header, *event_msg;
921
922   if (!is_pending)
923     {
924       GSList *l;
925
926       for (l = priv->events; l; l = l->next)
927         {
928           EventPriv *event = l->data;
929
930           if (event->public.contact == contact &&
931               event->func == event_pending_subscribe_func)
932             {
933               event_remove (event);
934               break;
935             }
936         }
937
938       return;
939     }
940
941   header = g_strdup_printf (_("Subscription requested by %s"),
942     empathy_contact_get_name (contact));
943
944   if (!EMP_STR_EMPTY (message))
945     event_msg = g_strdup_printf (_("\nMessage: %s"), message);
946   else
947     event_msg = NULL;
948
949   event_manager_add (manager, contact, EMPATHY_EVENT_TYPE_SUBSCRIPTION,
950       GTK_STOCK_DIALOG_QUESTION, header, event_msg, NULL,
951       event_pending_subscribe_func, NULL);
952
953   g_free (event_msg);
954   g_free (header);
955 }
956
957 static void
958 event_manager_presence_changed_cb (EmpathyContactMonitor *monitor,
959     EmpathyContact *contact,
960     TpConnectionPresenceType current,
961     TpConnectionPresenceType previous,
962     EmpathyEventManager *manager)
963 {
964   TpAccount *account;
965   gchar *header = NULL;
966   gboolean preference = FALSE;
967   EmpathyIdle *idle;
968
969   account = empathy_contact_get_account (contact);
970   idle = empathy_idle_dup_singleton ();
971
972   if (empathy_idle_account_is_just_connected (idle, account))
973     goto out;
974
975   if (tp_connection_presence_type_cmp_availability (previous,
976      TP_CONNECTION_PRESENCE_TYPE_OFFLINE) > 0)
977     {
978       /* contact was online */
979       empathy_conf_get_bool (empathy_conf_get (),
980                       EMPATHY_PREFS_NOTIFICATIONS_CONTACT_SIGNOUT, &preference);
981       if (preference && tp_connection_presence_type_cmp_availability (current,
982           TP_CONNECTION_PRESENCE_TYPE_OFFLINE) <= 0)
983         {
984           /* someone is logging off */
985           header = g_strdup_printf (_("%s is now offline."),
986             empathy_contact_get_name (contact));
987
988           event_manager_add (manager, contact, EMPATHY_EVENT_TYPE_PRESENCE,
989               "stock_person", header, NULL, NULL, NULL, NULL);
990         }
991     }
992   else
993     {
994       /* contact was offline */
995       empathy_conf_get_bool (empathy_conf_get (),
996                       EMPATHY_PREFS_NOTIFICATIONS_CONTACT_SIGNIN, &preference);
997       if (preference && tp_connection_presence_type_cmp_availability (current,
998           TP_CONNECTION_PRESENCE_TYPE_OFFLINE) > 0)
999         {
1000           /* someone is logging in */
1001           header = g_strdup_printf (_("%s is now online."),
1002             empathy_contact_get_name (contact));
1003
1004           event_manager_add (manager, contact, EMPATHY_EVENT_TYPE_PRESENCE,
1005               "stock_person", header, NULL, NULL, NULL, NULL);
1006         }
1007     }
1008   g_free (header);
1009
1010 out:
1011   g_object_unref (idle);
1012 }
1013
1014
1015 static GObject *
1016 event_manager_constructor (GType type,
1017                            guint n_props,
1018                            GObjectConstructParam *props)
1019 {
1020         GObject *retval;
1021
1022         if (manager_singleton) {
1023                 retval = g_object_ref (manager_singleton);
1024         } else {
1025                 retval = G_OBJECT_CLASS (empathy_event_manager_parent_class)->constructor
1026                         (type, n_props, props);
1027
1028                 manager_singleton = EMPATHY_EVENT_MANAGER (retval);
1029                 g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
1030         }
1031
1032         return retval;
1033 }
1034
1035 static void
1036 event_manager_finalize (GObject *object)
1037 {
1038   EmpathyEventManagerPriv *priv = GET_PRIV (object);
1039
1040   if (priv->ringing > 0)
1041     empathy_sound_stop (EMPATHY_SOUND_PHONE_INCOMING);
1042
1043   g_slist_foreach (priv->events, (GFunc) event_free, NULL);
1044   g_slist_free (priv->events);
1045   g_slist_foreach (priv->approvals, (GFunc) event_manager_approval_free, NULL);
1046   g_slist_free (priv->approvals);
1047   g_object_unref (priv->contact_manager);
1048   g_object_unref (priv->dispatcher);
1049 }
1050
1051 static void
1052 empathy_event_manager_class_init (EmpathyEventManagerClass *klass)
1053 {
1054   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1055
1056   object_class->finalize = event_manager_finalize;
1057   object_class->constructor = event_manager_constructor;
1058
1059   signals[EVENT_ADDED] =
1060     g_signal_new ("event-added",
1061       G_TYPE_FROM_CLASS (klass),
1062       G_SIGNAL_RUN_LAST,
1063       0,
1064       NULL, NULL,
1065       g_cclosure_marshal_VOID__POINTER,
1066       G_TYPE_NONE,
1067       1, G_TYPE_POINTER);
1068
1069   signals[EVENT_REMOVED] =
1070   g_signal_new ("event-removed",
1071       G_TYPE_FROM_CLASS (klass),
1072       G_SIGNAL_RUN_LAST,
1073       0,
1074       NULL, NULL,
1075       g_cclosure_marshal_VOID__POINTER,
1076       G_TYPE_NONE, 1, G_TYPE_POINTER);
1077
1078   signals[EVENT_UPDATED] =
1079   g_signal_new ("event-updated",
1080       G_TYPE_FROM_CLASS (klass),
1081       G_SIGNAL_RUN_LAST,
1082       0,
1083       NULL, NULL,
1084       g_cclosure_marshal_VOID__POINTER,
1085       G_TYPE_NONE, 1, G_TYPE_POINTER);
1086
1087
1088   g_type_class_add_private (object_class, sizeof (EmpathyEventManagerPriv));
1089 }
1090
1091 static void
1092 empathy_event_manager_init (EmpathyEventManager *manager)
1093 {
1094   EmpathyEventManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
1095     EMPATHY_TYPE_EVENT_MANAGER, EmpathyEventManagerPriv);
1096   EmpathyContactMonitor *monitor;
1097   EmpathyContactList *list_iface;
1098
1099   list_iface = EMPATHY_CONTACT_LIST (empathy_contact_manager_dup_singleton ());
1100   monitor = empathy_contact_list_get_monitor (list_iface);
1101   g_object_unref (list_iface);
1102
1103   manager->priv = priv;
1104
1105   priv->dispatcher = empathy_dispatcher_dup_singleton ();
1106   priv->contact_manager = empathy_contact_manager_dup_singleton ();
1107   g_signal_connect (priv->dispatcher, "approve",
1108     G_CALLBACK (event_manager_approve_channel_cb), manager);
1109   g_signal_connect (priv->contact_manager, "pendings-changed",
1110     G_CALLBACK (event_manager_pendings_changed_cb), manager);
1111   g_signal_connect (monitor, "contact-presence-changed",
1112     G_CALLBACK (event_manager_presence_changed_cb), manager);
1113 }
1114
1115 EmpathyEventManager *
1116 empathy_event_manager_dup_singleton (void)
1117 {
1118   return g_object_new (EMPATHY_TYPE_EVENT_MANAGER, NULL);
1119 }
1120
1121 GSList *
1122 empathy_event_manager_get_events (EmpathyEventManager *manager)
1123 {
1124   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
1125
1126   g_return_val_if_fail (EMPATHY_IS_EVENT_MANAGER (manager), NULL);
1127
1128   return priv->events;
1129 }
1130
1131 EmpathyEvent *
1132 empathy_event_manager_get_top_event (EmpathyEventManager *manager)
1133 {
1134   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
1135
1136   g_return_val_if_fail (EMPATHY_IS_EVENT_MANAGER (manager), NULL);
1137
1138   return priv->events ? priv->events->data : NULL;
1139 }
1140
1141 void
1142 empathy_event_activate (EmpathyEvent *event_public)
1143 {
1144   EventPriv *event = (EventPriv *) event_public;
1145
1146   g_return_if_fail (event_public != NULL);
1147
1148   if (event->func)
1149     event->func (event);
1150   else
1151     event_remove (event);
1152 }
1153
1154 void
1155 empathy_event_inhibit_updates (EmpathyEvent *event_public)
1156 {
1157   EventPriv *event = (EventPriv *) event_public;
1158
1159   g_return_if_fail (event_public != NULL);
1160
1161   event->inhibit = TRUE;
1162 }