]> git.0d.be Git - empathy.git/blob - src/empathy-event-manager.c
add empathy_event_approve()
[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 #include <telepathy-glib/simple-approver.h>
31
32 #include <libempathy/empathy-dispatcher.h>
33 #include <libempathy/empathy-idle.h>
34 #include <libempathy/empathy-tp-contact-factory.h>
35 #include <libempathy/empathy-contact-manager.h>
36 #include <libempathy/empathy-tp-chat.h>
37 #include <libempathy/empathy-tp-call.h>
38 #include <libempathy/empathy-tp-file.h>
39 #include <libempathy/empathy-utils.h>
40 #include <libempathy/empathy-call-factory.h>
41 #include <libempathy/empathy-gsettings.h>
42
43 #include <extensions/extensions.h>
44
45 #include <libempathy-gtk/empathy-images.h>
46 #include <libempathy-gtk/empathy-contact-dialogs.h>
47 #include <libempathy-gtk/empathy-sound.h>
48
49 #include "empathy-event-manager.h"
50 #include "empathy-main-window.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   TpChannelDispatchOperation *operation;
65   gulong invalidated_handler;
66   /* Remove contact if applicable */
67   EmpathyContact *contact;
68   /* option signal handler and it's instance */
69   gulong handler;
70   GObject *handler_instance;
71   /* optional accept widget */
72   GtkWidget *dialog;
73   /* Channel of the CDO that will be used during the approval */
74   TpChannel *main_channel;
75 } EventManagerApproval;
76
77 typedef struct {
78   EmpathyDispatcher *dispatcher;
79   TpBaseClient *approver;
80   EmpathyContactManager *contact_manager;
81   GSList *events;
82   /* Ongoing approvals */
83   GSList *approvals;
84
85   gint ringing;
86 } EmpathyEventManagerPriv;
87
88 typedef struct _EventPriv EventPriv;
89 typedef void (*EventFunc) (EventPriv *event);
90
91 struct _EventPriv {
92   EmpathyEvent public;
93   EmpathyEventManager *manager;
94   EventManagerApproval *approval;
95   EventFunc func;
96   gboolean inhibit;
97   gpointer user_data;
98   guint autoremove_timeout_id;
99 };
100
101 enum {
102   EVENT_ADDED,
103   EVENT_REMOVED,
104   EVENT_UPDATED,
105   LAST_SIGNAL
106 };
107
108 static guint signals[LAST_SIGNAL];
109
110 G_DEFINE_TYPE (EmpathyEventManager, empathy_event_manager, G_TYPE_OBJECT);
111
112 static EmpathyEventManager * manager_singleton = NULL;
113
114 static EventManagerApproval *
115 event_manager_approval_new (EmpathyEventManager *manager,
116   TpChannelDispatchOperation *operation,
117   TpChannel *main_channel)
118 {
119   EventManagerApproval *result = g_slice_new0 (EventManagerApproval);
120   result->operation = g_object_ref (operation);
121   result->manager = manager;
122   result->main_channel = g_object_ref (main_channel);
123
124   return result;
125 }
126
127 static void
128 event_manager_approval_free (EventManagerApproval *approval)
129 {
130   g_signal_handler_disconnect (approval->operation,
131     approval->invalidated_handler);
132   g_object_unref (approval->operation);
133
134   g_object_unref (approval->main_channel);
135
136   if (approval->handler != 0)
137     g_signal_handler_disconnect (approval->handler_instance,
138       approval->handler);
139
140   if (approval->handler_instance != NULL)
141     g_object_unref (approval->handler_instance);
142
143   if (approval->contact != NULL)
144     g_object_unref (approval->contact);
145
146   if (approval->dialog != NULL)
147     {
148       gtk_widget_destroy (approval->dialog);
149     }
150
151   g_slice_free (EventManagerApproval, approval);
152 }
153
154 static void event_remove (EventPriv *event);
155
156 static void
157 event_free (EventPriv *event)
158 {
159   g_free (event->public.icon_name);
160   g_free (event->public.header);
161   g_free (event->public.message);
162
163   if (event->autoremove_timeout_id != 0)
164     g_source_remove (event->autoremove_timeout_id);
165
166   if (event->public.contact)
167     {
168       g_object_unref (event->public.contact);
169     }
170
171   g_slice_free (EventPriv, event);
172 }
173
174 static void
175 event_remove (EventPriv *event)
176 {
177   EmpathyEventManagerPriv *priv = GET_PRIV (event->manager);
178
179   DEBUG ("Removing event %p", event);
180
181   priv->events = g_slist_remove (priv->events, event);
182   g_signal_emit (event->manager, signals[EVENT_REMOVED], 0, event);
183   event_free (event);
184 }
185
186 static gboolean
187 autoremove_event_timeout_cb (EventPriv *event)
188 {
189   event->autoremove_timeout_id = 0;
190   event_remove (event);
191   return FALSE;
192 }
193
194 static void
195 event_manager_add (EmpathyEventManager *manager,
196     EmpathyContact *contact,
197     EmpathyEventType type,
198     const gchar *icon_name,
199     const gchar *header,
200     const gchar *message,
201     EventManagerApproval *approval,
202     EventFunc func,
203     gpointer user_data)
204 {
205   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
206   EventPriv               *event;
207
208   event = g_slice_new0 (EventPriv);
209   event->public.contact = contact ? g_object_ref (contact) : NULL;
210   event->public.type = type;
211   event->public.icon_name = g_strdup (icon_name);
212   event->public.header = g_strdup (header);
213   event->public.message = g_strdup (message);
214   event->public.must_ack = (func != NULL);
215   event->inhibit = FALSE;
216   event->func = func;
217   event->user_data = user_data;
218   event->manager = manager;
219   event->approval = approval;
220
221   DEBUG ("Adding event %p", event);
222   priv->events = g_slist_prepend (priv->events, event);
223   g_signal_emit (event->manager, signals[EVENT_ADDED], 0, event);
224
225   if (!event->public.must_ack)
226     {
227       event->autoremove_timeout_id = g_timeout_add_seconds (
228           NOTIFICATION_TIMEOUT, (GSourceFunc) autoremove_event_timeout_cb,
229           event);
230     }
231 }
232
233 static void
234 handle_with_cb (GObject *source,
235     GAsyncResult *result,
236     gpointer user_data)
237 {
238   TpChannelDispatchOperation *cdo = TP_CHANNEL_DISPATCH_OPERATION (source);
239   GError *error = NULL;
240
241   if (!tp_channel_dispatch_operation_handle_with_finish (cdo, result, &error))
242     {
243       DEBUG ("HandleWith failed: %s\n", error->message);
244       g_error_free (error);
245     }
246 }
247
248 static void
249 handle_with_time_cb (GObject *source,
250     GAsyncResult *result,
251     gpointer user_data)
252 {
253   TpChannelDispatchOperation *cdo = TP_CHANNEL_DISPATCH_OPERATION (source);
254   GError *error = NULL;
255
256   if (!tp_channel_dispatch_operation_handle_with_time_finish (cdo, result,
257         &error))
258     {
259       if (g_error_matches (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED))
260         {
261           EventManagerApproval *approval = user_data;
262
263           DEBUG ("HandleWithTime() is not implemented, falling back to "
264               "HandleWith(). Please upgrade to telepathy-mission-control "
265               "5.5.0 or later");
266
267           tp_channel_dispatch_operation_handle_with_async (approval->operation,
268               NULL, handle_with_cb, approval);
269         }
270       else
271         {
272           DEBUG ("HandleWithTime failed: %s\n", error->message);
273         }
274       g_error_free (error);
275     }
276 }
277
278 static void
279 event_manager_approval_approve (EventManagerApproval *approval)
280 {
281   gint64 timestamp = gtk_get_current_event_time ();
282
283   if (timestamp == GDK_CURRENT_TIME)
284     timestamp = EMPATHY_DISPATCHER_CURRENT_TIME;
285
286   g_assert (approval->operation != NULL);
287
288   tp_channel_dispatch_operation_handle_with_time_async (approval->operation,
289       NULL, timestamp, handle_with_time_cb, approval);
290 }
291
292 static void
293 event_channel_process_func (EventPriv *event)
294 {
295   event_manager_approval_approve (event->approval);
296 }
297
298 static void
299 event_text_channel_process_func (EventPriv *event)
300 {
301   EmpathyTpChat *tp_chat;
302   gint64 timestamp = gtk_get_current_event_time ();
303   if (timestamp == GDK_CURRENT_TIME)
304     timestamp = EMPATHY_DISPATCHER_CURRENT_TIME;
305
306   if (event->approval->handler != 0)
307     {
308       tp_chat = EMPATHY_TP_CHAT (event->approval->handler_instance);
309
310       g_signal_handler_disconnect (tp_chat, event->approval->handler);
311       event->approval->handler = 0;
312     }
313
314   event_manager_approval_approve (event->approval);
315 }
316
317 static EventPriv *
318 event_lookup_by_approval (EmpathyEventManager *manager,
319   EventManagerApproval *approval)
320 {
321   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
322   GSList *l;
323   EventPriv *retval = NULL;
324
325   for (l = priv->events; l; l = l->next)
326     {
327       EventPriv *event = l->data;
328
329       if (event->approval == approval)
330         {
331           retval = event;
332           break;
333         }
334     }
335
336   return retval;
337 }
338
339 static void
340 event_update (EmpathyEventManager *manager, EventPriv *event,
341   const char *icon_name, const char *header, const char *msg)
342 {
343   g_free (event->public.icon_name);
344   g_free (event->public.header);
345   g_free (event->public.message);
346
347   event->public.icon_name = g_strdup (icon_name);
348   event->public.header = g_strdup (header);
349   event->public.message = g_strdup (msg);
350
351   g_signal_emit (manager, signals[EVENT_UPDATED], 0, event);
352 }
353
354 static void
355 reject_channel_claim_cb (GObject *source,
356     GAsyncResult *result,
357     gpointer user_data)
358 {
359   TpChannelDispatchOperation *cdo = TP_CHANNEL_DISPATCH_OPERATION (source);
360   GError *error = NULL;
361
362   if (!tp_channel_dispatch_operation_claim_finish (cdo, result, &error))
363     {
364       DEBUG ("Failed to claim channel: %s", error->message);
365
366       g_error_free (error);
367       goto out;
368     }
369
370   if (EMPATHY_IS_TP_CALL (user_data))
371     {
372       empathy_tp_call_close (user_data);
373     }
374   else if (EMPATHY_IS_TP_CHAT (user_data))
375     {
376       empathy_tp_chat_leave (user_data);
377     }
378
379 out:
380   g_object_unref (user_data);
381 }
382
383 static void
384 reject_approval (EventManagerApproval *approval)
385 {
386   /* We have to claim the channel before closing it */
387   tp_channel_dispatch_operation_claim_async (approval->operation,
388       reject_channel_claim_cb, g_object_ref (approval->handler_instance));
389 }
390
391 static void
392 event_manager_call_window_confirmation_dialog_response_cb (GtkDialog *dialog,
393   gint response, gpointer user_data)
394 {
395   EventManagerApproval *approval = user_data;
396
397   gtk_widget_destroy (approval->dialog);
398   approval->dialog = NULL;
399
400   if (response != GTK_RESPONSE_ACCEPT)
401     {
402       reject_approval (approval);
403     }
404   else
405     {
406       event_manager_approval_approve (approval);
407     }
408 }
409
410 static void
411 event_channel_process_voip_func (EventPriv *event)
412 {
413   GtkWidget *dialog;
414   GtkWidget *button;
415   GtkWidget *image;
416   EmpathyTpCall *call;
417   gboolean video;
418
419   if (event->approval->dialog != NULL)
420     {
421       gtk_window_present (GTK_WINDOW (event->approval->dialog));
422       return;
423     }
424
425   call = EMPATHY_TP_CALL (event->approval->handler_instance);
426
427   video = empathy_tp_call_has_initial_video (call);
428
429   dialog = gtk_message_dialog_new (NULL, 0,
430       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
431       video ? _("Incoming video call"): _("Incoming call"));
432
433   gtk_message_dialog_format_secondary_text (
434     GTK_MESSAGE_DIALOG (dialog), video ?
435       _("%s is video calling you. Do you want to answer?"):
436       _("%s is calling you. Do you want to answer?"),
437       empathy_contact_get_name (event->approval->contact));
438
439   /* Set image of the dialog */
440   if (video)
441     {
442       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
443           GTK_ICON_SIZE_DIALOG);
444     }
445   else
446     {
447       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP,
448           GTK_ICON_SIZE_DIALOG);
449     }
450
451   gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
452   gtk_widget_show (image);
453
454   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
455       GTK_RESPONSE_OK);
456
457   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
458       _("_Reject"), GTK_RESPONSE_REJECT);
459   image = gtk_image_new_from_icon_name ("call-stop",
460     GTK_ICON_SIZE_BUTTON);
461   gtk_button_set_image (GTK_BUTTON (button), image);
462
463   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
464       _("_Answer"), GTK_RESPONSE_ACCEPT);
465
466   image = gtk_image_new_from_icon_name ("call-start", GTK_ICON_SIZE_BUTTON);
467   gtk_button_set_image (GTK_BUTTON (button), image);
468
469   g_signal_connect (dialog, "response",
470       G_CALLBACK (event_manager_call_window_confirmation_dialog_response_cb),
471       event->approval);
472
473   gtk_widget_show (dialog);
474
475   event->approval->dialog = dialog;
476 }
477
478 static void
479 event_manager_chat_message_received_cb (EmpathyTpChat *tp_chat,
480   EmpathyMessage *message,
481   EventManagerApproval *approval)
482 {
483   GtkWidget       *window = empathy_main_window_dup ();
484   EmpathyContact  *sender;
485   const gchar     *header;
486   const gchar     *msg;
487   TpChannel       *channel;
488   EventPriv       *event;
489
490   /* try to update the event if it's referring to a chat which is already in the
491    * queue. */
492   event = event_lookup_by_approval (approval->manager, approval);
493
494   sender = empathy_message_get_sender (message);
495   header = empathy_contact_get_name (sender);
496   msg = empathy_message_get_body (message);
497
498   channel = empathy_tp_chat_get_channel (tp_chat);
499
500   if (event != NULL)
501     event_update (approval->manager, event, EMPATHY_IMAGE_NEW_MESSAGE, header,
502         msg);
503   else
504     event_manager_add (approval->manager, sender, EMPATHY_EVENT_TYPE_CHAT,
505         EMPATHY_IMAGE_NEW_MESSAGE, header, msg, approval,
506         event_text_channel_process_func, NULL);
507
508   empathy_sound_play (window, EMPATHY_SOUND_CONVERSATION_NEW);
509
510   g_object_unref (window);
511 }
512
513 static void
514 event_manager_approval_done (EventManagerApproval *approval)
515 {
516   EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager);
517   GSList                  *l;
518
519   if (approval->operation != NULL)
520     {
521       GQuark channel_type;
522
523       channel_type = tp_channel_get_channel_type_id (approval->main_channel);
524
525       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
526         {
527           priv->ringing--;
528           if (priv->ringing == 0)
529             empathy_sound_stop (EMPATHY_SOUND_PHONE_INCOMING);
530         }
531     }
532
533   priv->approvals = g_slist_remove (priv->approvals, approval);
534
535   for (l = priv->events; l; l = l->next)
536     {
537       EventPriv *event = l->data;
538
539       if (event->approval == approval)
540         {
541           event_remove (event);
542           break;
543         }
544     }
545
546   event_manager_approval_free (approval);
547 }
548
549 static void
550 cdo_invalidated_cb (TpProxy *cdo,
551     guint domain,
552     gint code,
553     gchar *message,
554     EventManagerApproval *approval)
555 {
556   DEBUG ("ChannelDispatchOperation has been invalidated: %s", message);
557
558   event_manager_approval_done (approval);
559 }
560
561 static void
562 event_manager_media_channel_got_contact (EventManagerApproval *approval)
563 {
564   EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager);
565   GtkWidget *window = empathy_main_window_dup ();
566   gchar *header;
567   EmpathyTpCall *call;
568   gboolean video;
569
570   call = EMPATHY_TP_CALL (approval->handler_instance);
571
572   video = empathy_tp_call_has_initial_video (call);
573
574   header = g_strdup_printf (
575     video ? _("Incoming video call from %s") :_("Incoming call from %s"),
576     empathy_contact_get_name (approval->contact));
577
578   event_manager_add (approval->manager, approval->contact,
579       EMPATHY_EVENT_TYPE_VOIP,
580       video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
581       header, NULL, approval,
582       event_channel_process_voip_func, NULL);
583
584   g_free (header);
585
586   priv->ringing++;
587   if (priv->ringing == 1)
588     empathy_sound_start_playing (window,
589         EMPATHY_SOUND_PHONE_INCOMING, MS_BETWEEN_RING);
590
591   g_object_unref (window);
592 }
593
594 static void
595 event_manager_media_channel_contact_changed_cb (EmpathyTpCall *call,
596   GParamSpec *param, EventManagerApproval *approval)
597 {
598   EmpathyContact *contact;
599
600   g_object_get (G_OBJECT (call), "contact", &contact, NULL);
601
602   if (contact == NULL)
603     return;
604
605   approval->contact = contact;
606   event_manager_media_channel_got_contact (approval);
607 }
608
609 static void
610 invite_dialog_response_cb (GtkDialog *dialog,
611                            gint response,
612                            EventManagerApproval *approval)
613 {
614   EmpathyTpChat *tp_chat;
615
616   gtk_widget_destroy (GTK_WIDGET (approval->dialog));
617   approval->dialog = NULL;
618
619   tp_chat = EMPATHY_TP_CHAT (approval->handler_instance);
620
621   if (response != GTK_RESPONSE_OK)
622     {
623       /* close channel */
624       DEBUG ("Muc invitation rejected");
625
626       reject_approval (approval);
627
628       return;
629     }
630
631   DEBUG ("Muc invitation accepted");
632
633   /* We'll join the room when handling the channel */
634   event_manager_approval_approve (approval);
635 }
636
637 static void
638 event_room_channel_process_func (EventPriv *event)
639 {
640   GtkWidget *dialog, *button, *image;
641   TpChannel *channel = event->approval->main_channel;
642
643   if (event->approval->dialog != NULL)
644     {
645       gtk_window_present (GTK_WINDOW (event->approval->dialog));
646       return;
647     }
648
649   /* create dialog */
650   dialog = gtk_message_dialog_new (NULL, 0,
651       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Room invitation"));
652
653   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
654       _("%s is inviting you to join %s"),
655       empathy_contact_get_name (event->approval->contact),
656       tp_channel_get_identifier (channel));
657
658   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
659       GTK_RESPONSE_OK);
660
661   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
662       _("_Decline"), GTK_RESPONSE_CANCEL);
663   image = gtk_image_new_from_icon_name (GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
664   gtk_button_set_image (GTK_BUTTON (button), image);
665
666   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
667       _("_Join"), GTK_RESPONSE_OK);
668   image = gtk_image_new_from_icon_name (GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON);
669   gtk_button_set_image (GTK_BUTTON (button), image);
670
671   g_signal_connect (dialog, "response",
672       G_CALLBACK (invite_dialog_response_cb), event->approval);
673
674   gtk_widget_show (dialog);
675
676   event->approval->dialog = dialog;
677 }
678
679 static void
680 event_manager_muc_invite_got_contact_cb (TpConnection *connection,
681                                          EmpathyContact *contact,
682                                          const GError *error,
683                                          gpointer user_data,
684                                          GObject *object)
685 {
686   EventManagerApproval *approval = (EventManagerApproval *) user_data;
687   GtkWidget *window = empathy_main_window_dup ();
688   const gchar *invite_msg;
689   gchar *msg;
690   TpHandle self_handle;
691
692   if (error != NULL)
693     {
694       /* FIXME: We should probably still display the event */
695       DEBUG ("Error: %s", error->message);
696       return;
697     }
698
699   approval->contact = g_object_ref (contact);
700
701   self_handle = tp_channel_group_get_self_handle (approval->main_channel);
702   tp_channel_group_get_local_pending_info (approval->main_channel, self_handle,
703       NULL, NULL, &invite_msg);
704
705   msg = g_strdup_printf (_("%s invited you to join %s"),
706       empathy_contact_get_name (approval->contact),
707       tp_channel_get_identifier (approval->main_channel));
708
709   event_manager_add (approval->manager, approval->contact,
710       EMPATHY_EVENT_TYPE_CHAT, EMPATHY_IMAGE_GROUP_MESSAGE, msg, invite_msg,
711       approval, event_room_channel_process_func, NULL);
712
713   empathy_sound_play (window, EMPATHY_SOUND_CONVERSATION_NEW);
714
715   g_free (msg);
716   g_object_unref (window);
717 }
718
719 static void
720 event_manager_ft_got_contact_cb (TpConnection *connection,
721                                  EmpathyContact *contact,
722                                  const GError *error,
723                                  gpointer user_data,
724                                  GObject *object)
725 {
726   EventManagerApproval *approval = (EventManagerApproval *) user_data;
727   GtkWidget *window = empathy_main_window_dup ();
728   char *header;
729
730   approval->contact = g_object_ref (contact);
731
732   header = g_strdup_printf (_("Incoming file transfer from %s"),
733                             empathy_contact_get_name (approval->contact));
734
735   event_manager_add (approval->manager, approval->contact,
736       EMPATHY_EVENT_TYPE_TRANSFER, EMPATHY_IMAGE_DOCUMENT_SEND, header, NULL,
737       approval, event_channel_process_func, NULL);
738
739   /* FIXME better sound for incoming file transfers ?*/
740   empathy_sound_play (window, EMPATHY_SOUND_CONVERSATION_NEW);
741
742   g_free (header);
743   g_object_unref (window);
744 }
745
746 /* If there is a file-transfer or media channel consider it as the
747  * main one. */
748 static TpChannel *
749 find_main_channel (GList *channels)
750 {
751   GList *l;
752   TpChannel *text = NULL;
753
754   for (l = channels; l != NULL; l = g_list_next (l))
755     {
756       TpChannel *channel = l->data;
757       GQuark channel_type;
758
759       if (tp_proxy_get_invalidated (channel) != NULL)
760         continue;
761
762       channel_type = tp_channel_get_channel_type_id (channel);
763
764       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
765           channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_FILE_TRANSFER)
766         return channel;
767
768       else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
769         text = channel;
770     }
771
772   return text;
773 }
774
775 static void
776 approve_channels (TpSimpleApprover *approver,
777     TpAccount *account,
778     TpConnection *connection,
779     GList *channels,
780     TpChannelDispatchOperation *dispatch_operation,
781     TpAddDispatchOperationContext *context,
782     gpointer user_data)
783 {
784   EmpathyEventManager *self = user_data;
785   EmpathyEventManagerPriv *priv = GET_PRIV (self);
786   TpChannel *channel;
787   EventManagerApproval *approval;
788   GQuark channel_type;
789
790   channel = find_main_channel (channels);
791   if (channel == NULL)
792     {
793       GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
794           "Unknown channel type" };
795
796       DEBUG ("Failed to find the main channel; ignoring");
797
798       tp_add_dispatch_operation_context_fail (context, &error);
799       return;
800     }
801
802   approval = event_manager_approval_new (self, dispatch_operation, channel);
803   priv->approvals = g_slist_prepend (priv->approvals, approval);
804
805   approval->invalidated_handler = g_signal_connect (dispatch_operation,
806       "invalidated", G_CALLBACK (cdo_invalidated_cb), approval);
807
808   channel_type = tp_channel_get_channel_type_id (channel);
809
810   if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
811     {
812       EmpathyTpChat *tp_chat;
813
814       tp_chat = empathy_tp_chat_new (channel);
815       approval->handler_instance = G_OBJECT (tp_chat);
816
817       if (tp_proxy_has_interface (channel, TP_IFACE_CHANNEL_INTERFACE_GROUP))
818         {
819           /* Are we in local-pending ? */
820           TpHandle inviter;
821
822           if (empathy_tp_chat_is_invited (tp_chat, &inviter))
823             {
824               /* We are invited to a room */
825               DEBUG ("Have been invited to %s. Ask user if he wants to accept",
826                   tp_channel_get_identifier (channel));
827
828               empathy_tp_contact_factory_get_from_handle (connection,
829                   inviter, event_manager_muc_invite_got_contact_cb,
830                   approval, NULL, G_OBJECT (self));
831
832               goto out;
833             }
834
835           /* We are not invited, approve the channel right now */
836           tp_add_dispatch_operation_context_accept (context);
837
838           event_manager_approval_approve (approval);
839           return;
840         }
841
842       /* 1-1 text channel, wait for the first message */
843       approval->handler = g_signal_connect (tp_chat, "message-received",
844         G_CALLBACK (event_manager_chat_message_received_cb), approval);
845     }
846   else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
847     {
848       EmpathyContact *contact;
849       EmpathyTpCall *call = empathy_tp_call_new (channel);
850
851       approval->handler_instance = G_OBJECT (call);
852
853       g_object_get (G_OBJECT (call), "contact", &contact, NULL);
854
855       if (contact == NULL)
856         {
857           g_signal_connect (call, "notify::contact",
858             G_CALLBACK (event_manager_media_channel_contact_changed_cb),
859             approval);
860         }
861       else
862         {
863           approval->contact = contact;
864           event_manager_media_channel_got_contact (approval);
865         }
866
867     }
868   else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_FILE_TRANSFER)
869     {
870       TpHandle handle;
871
872       handle = tp_channel_get_handle (channel, NULL);
873
874       connection = tp_channel_borrow_connection (channel);
875       empathy_tp_contact_factory_get_from_handle (connection, handle,
876         event_manager_ft_got_contact_cb, approval, NULL, G_OBJECT (self));
877     }
878   else
879     {
880       GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
881           "Invalid channel type" };
882
883       DEBUG ("Unknown channel type (%s), ignoring..",
884           g_quark_to_string (channel_type));
885
886       tp_add_dispatch_operation_context_fail (context, &error);
887       return;
888     }
889
890 out:
891   tp_add_dispatch_operation_context_accept (context);
892 }
893
894 static void
895 event_pending_subscribe_func (EventPriv *event)
896 {
897   empathy_subscription_dialog_show (event->public.contact, NULL);
898   event_remove (event);
899 }
900
901 static void
902 event_manager_pendings_changed_cb (EmpathyContactList  *list,
903   EmpathyContact *contact, EmpathyContact *actor,
904   guint reason, gchar *message, gboolean is_pending,
905   EmpathyEventManager *manager)
906 {
907   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
908   gchar                   *header, *event_msg;
909
910   if (!is_pending)
911     {
912       GSList *l;
913
914       for (l = priv->events; l; l = l->next)
915         {
916           EventPriv *event = l->data;
917
918           if (event->public.contact == contact &&
919               event->func == event_pending_subscribe_func)
920             {
921               event_remove (event);
922               break;
923             }
924         }
925
926       return;
927     }
928
929   header = g_strdup_printf (_("Subscription requested by %s"),
930     empathy_contact_get_name (contact));
931
932   if (!EMP_STR_EMPTY (message))
933     event_msg = g_strdup_printf (_("\nMessage: %s"), message);
934   else
935     event_msg = NULL;
936
937   event_manager_add (manager, contact, EMPATHY_EVENT_TYPE_SUBSCRIPTION,
938       GTK_STOCK_DIALOG_QUESTION, header, event_msg, NULL,
939       event_pending_subscribe_func, NULL);
940
941   g_free (event_msg);
942   g_free (header);
943 }
944
945 static void
946 event_manager_presence_changed_cb (EmpathyContact *contact,
947     TpConnectionPresenceType current,
948     TpConnectionPresenceType previous,
949     EmpathyEventManager *manager)
950 {
951   TpAccount *account;
952   gchar *header = NULL;
953   EmpathyIdle *idle;
954   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
955   GtkWidget *window = empathy_main_window_dup ();
956
957   account = empathy_contact_get_account (contact);
958   idle = empathy_idle_dup_singleton ();
959
960   if (empathy_idle_account_is_just_connected (idle, account))
961     goto out;
962
963   if (tp_connection_presence_type_cmp_availability (previous,
964         TP_CONNECTION_PRESENCE_TYPE_OFFLINE) > 0)
965     {
966       /* contact was online */
967       if (tp_connection_presence_type_cmp_availability (current,
968           TP_CONNECTION_PRESENCE_TYPE_OFFLINE) <= 0)
969         {
970           /* someone is logging off */
971           empathy_sound_play (window, EMPATHY_SOUND_CONTACT_DISCONNECTED);
972
973           if (g_settings_get_boolean (gsettings,
974                 EMPATHY_PREFS_NOTIFICATIONS_CONTACT_SIGNOUT))
975             {
976               header = g_strdup_printf (_("%s is now offline."),
977                   empathy_contact_get_name (contact));
978
979               event_manager_add (manager, contact, EMPATHY_EVENT_TYPE_PRESENCE,
980                   EMPATHY_IMAGE_AVATAR_DEFAULT, header, NULL, NULL, NULL, NULL);
981             }
982         }
983     }
984   else
985     {
986       /* contact was offline */
987       if (tp_connection_presence_type_cmp_availability (current,
988             TP_CONNECTION_PRESENCE_TYPE_OFFLINE) > 0)
989         {
990           /* someone is logging in */
991           empathy_sound_play (window, EMPATHY_SOUND_CONTACT_CONNECTED);
992
993           if (g_settings_get_boolean (gsettings,
994                 EMPATHY_PREFS_NOTIFICATIONS_CONTACT_SIGNIN))
995             {
996               header = g_strdup_printf (_("%s is now online."),
997                   empathy_contact_get_name (contact));
998
999               event_manager_add (manager, contact, EMPATHY_EVENT_TYPE_PRESENCE,
1000                   EMPATHY_IMAGE_AVATAR_DEFAULT, header, NULL, NULL, NULL, NULL);
1001             }
1002         }
1003     }
1004   g_free (header);
1005
1006 out:
1007   g_object_unref (idle);
1008   g_object_unref (gsettings);
1009   g_object_unref (window);
1010 }
1011
1012 static void
1013 event_manager_members_changed_cb (EmpathyContactList  *list,
1014     EmpathyContact *contact,
1015     EmpathyContact *actor,
1016     guint reason,
1017     gchar *message,
1018     gboolean is_member,
1019     EmpathyEventManager *manager)
1020 {
1021   if (is_member)
1022     g_signal_connect (contact, "presence-changed",
1023         G_CALLBACK (event_manager_presence_changed_cb), manager);
1024   else
1025     g_signal_handlers_disconnect_by_func (contact,
1026         event_manager_presence_changed_cb, manager);
1027 }
1028
1029 static GObject *
1030 event_manager_constructor (GType type,
1031                            guint n_props,
1032                            GObjectConstructParam *props)
1033 {
1034         GObject *retval;
1035
1036         if (manager_singleton) {
1037                 retval = g_object_ref (manager_singleton);
1038         } else {
1039                 retval = G_OBJECT_CLASS (empathy_event_manager_parent_class)->constructor
1040                         (type, n_props, props);
1041
1042                 manager_singleton = EMPATHY_EVENT_MANAGER (retval);
1043                 g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
1044         }
1045
1046         return retval;
1047 }
1048
1049 static void
1050 event_manager_finalize (GObject *object)
1051 {
1052   EmpathyEventManagerPriv *priv = GET_PRIV (object);
1053
1054   if (priv->ringing > 0)
1055     empathy_sound_stop (EMPATHY_SOUND_PHONE_INCOMING);
1056
1057   g_slist_foreach (priv->events, (GFunc) event_free, NULL);
1058   g_slist_free (priv->events);
1059   g_slist_foreach (priv->approvals, (GFunc) event_manager_approval_free, NULL);
1060   g_slist_free (priv->approvals);
1061   g_object_unref (priv->contact_manager);
1062   g_object_unref (priv->dispatcher);
1063   g_object_unref (priv->approver);
1064 }
1065
1066 static void
1067 empathy_event_manager_class_init (EmpathyEventManagerClass *klass)
1068 {
1069   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1070
1071   object_class->finalize = event_manager_finalize;
1072   object_class->constructor = event_manager_constructor;
1073
1074   signals[EVENT_ADDED] =
1075     g_signal_new ("event-added",
1076       G_TYPE_FROM_CLASS (klass),
1077       G_SIGNAL_RUN_LAST,
1078       0,
1079       NULL, NULL,
1080       g_cclosure_marshal_VOID__POINTER,
1081       G_TYPE_NONE,
1082       1, G_TYPE_POINTER);
1083
1084   signals[EVENT_REMOVED] =
1085   g_signal_new ("event-removed",
1086       G_TYPE_FROM_CLASS (klass),
1087       G_SIGNAL_RUN_LAST,
1088       0,
1089       NULL, NULL,
1090       g_cclosure_marshal_VOID__POINTER,
1091       G_TYPE_NONE, 1, G_TYPE_POINTER);
1092
1093   signals[EVENT_UPDATED] =
1094   g_signal_new ("event-updated",
1095       G_TYPE_FROM_CLASS (klass),
1096       G_SIGNAL_RUN_LAST,
1097       0,
1098       NULL, NULL,
1099       g_cclosure_marshal_VOID__POINTER,
1100       G_TYPE_NONE, 1, G_TYPE_POINTER);
1101
1102
1103   g_type_class_add_private (object_class, sizeof (EmpathyEventManagerPriv));
1104 }
1105
1106 static void
1107 empathy_event_manager_init (EmpathyEventManager *manager)
1108 {
1109   EmpathyEventManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
1110     EMPATHY_TYPE_EVENT_MANAGER, EmpathyEventManagerPriv);
1111   TpDBusDaemon *dbus;
1112   GError *error = NULL;
1113
1114   manager->priv = priv;
1115
1116   priv->dispatcher = empathy_dispatcher_dup_singleton ();
1117   priv->contact_manager = empathy_contact_manager_dup_singleton ();
1118   g_signal_connect (priv->contact_manager, "pendings-changed",
1119     G_CALLBACK (event_manager_pendings_changed_cb), manager);
1120
1121   g_signal_connect (priv->contact_manager, "members-changed",
1122     G_CALLBACK (event_manager_members_changed_cb), manager);
1123
1124   dbus = tp_dbus_daemon_dup (&error);
1125   if (dbus == NULL)
1126     {
1127       DEBUG ("Failed to get TpDBusDaemon: %s", error->message);
1128       g_error_free (error);
1129       return;
1130     }
1131
1132   priv->approver = tp_simple_approver_new (dbus, "Empathy.EventManager", FALSE,
1133       approve_channels, manager, NULL);
1134
1135   /* Private text channels */
1136   tp_base_client_take_approver_filter (priv->approver,
1137       tp_asv_new (
1138         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
1139         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
1140         NULL));
1141
1142   /* Muc text channels */
1143   tp_base_client_take_approver_filter (priv->approver,
1144       tp_asv_new (
1145         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
1146         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
1147         NULL));
1148
1149   /* File transfer */
1150   tp_base_client_take_approver_filter (priv->approver,
1151       tp_asv_new (
1152         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
1153           TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
1154         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
1155         NULL));
1156
1157   /* Calls */
1158   tp_base_client_take_approver_filter (priv->approver,
1159       tp_asv_new (
1160         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
1161           TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
1162         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
1163         NULL));
1164
1165   if (!tp_base_client_register (priv->approver, &error))
1166     {
1167       DEBUG ("Failed to register Approver: %s", error->message);
1168       g_error_free (error);
1169     }
1170
1171   g_object_unref (dbus);
1172 }
1173
1174 EmpathyEventManager *
1175 empathy_event_manager_dup_singleton (void)
1176 {
1177   return g_object_new (EMPATHY_TYPE_EVENT_MANAGER, NULL);
1178 }
1179
1180 GSList *
1181 empathy_event_manager_get_events (EmpathyEventManager *manager)
1182 {
1183   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
1184
1185   g_return_val_if_fail (EMPATHY_IS_EVENT_MANAGER (manager), NULL);
1186
1187   return priv->events;
1188 }
1189
1190 EmpathyEvent *
1191 empathy_event_manager_get_top_event (EmpathyEventManager *manager)
1192 {
1193   EmpathyEventManagerPriv *priv = GET_PRIV (manager);
1194
1195   g_return_val_if_fail (EMPATHY_IS_EVENT_MANAGER (manager), NULL);
1196
1197   return priv->events ? priv->events->data : NULL;
1198 }
1199
1200 void
1201 empathy_event_activate (EmpathyEvent *event_public)
1202 {
1203   EventPriv *event = (EventPriv *) event_public;
1204
1205   g_return_if_fail (event_public != NULL);
1206
1207   if (event->func)
1208     event->func (event);
1209   else
1210     event_remove (event);
1211 }
1212
1213 void
1214 empathy_event_inhibit_updates (EmpathyEvent *event_public)
1215 {
1216   EventPriv *event = (EventPriv *) event_public;
1217
1218   g_return_if_fail (event_public != NULL);
1219
1220   event->inhibit = TRUE;
1221 }
1222
1223 void
1224 empathy_event_approve (EmpathyEvent *event_public)
1225 {
1226   EventPriv *event = (EventPriv *) event_public;
1227
1228   g_return_if_fail (event_public != NULL);
1229
1230   event_manager_approval_approve (event->approval);
1231 }