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