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