]> git.0d.be Git - empathy.git/blob - src/empathy-call-observer.c
Update Simplified Chinese help translation.
[empathy.git] / src / empathy-call-observer.c
1 /*
2  * Copyright (C) 2011 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: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
19  */
20
21 #include <config.h>
22
23 #include <glib/gi18n-lib.h>
24
25 #include <telepathy-glib/telepathy-glib.h>
26
27 #include <telepathy-yell/telepathy-yell.h>
28
29 #include <libnotify/notification.h>
30
31 #include <libempathy/empathy-channel-factory.h>
32 #include <libempathy/empathy-utils.h>
33
34 #include <libempathy-gtk/empathy-images.h>
35 #include <libempathy-gtk/empathy-notify-manager.h>
36
37 #include <extensions/extensions.h>
38
39 #include "empathy-call-observer.h"
40
41 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
42 #include <libempathy/empathy-debug.h>
43
44 struct _EmpathyCallObserverPriv {
45   EmpathyNotifyManager *notify_mgr;
46
47   TpBaseClient *observer;
48
49   /* Ongoing calls, as reffed TpChannels */
50   GList *channels;
51 };
52
53 /* The Call Observer looks at incoming and outgoing calls, and
54  * autorejects incoming ones if there are ongoing ones, since
55  * we don't cope with simultaneous calls quite well yet.
56  * At some point, we should ask the user if he wants to put the
57  * current call on hold and answer the incoming one instead,
58  * see https://bugzilla.gnome.org/show_bug.cgi?id=623348
59  */
60 G_DEFINE_TYPE (EmpathyCallObserver, empathy_call_observer, G_TYPE_OBJECT);
61
62 static EmpathyCallObserver * observer_singleton = NULL;
63
64 static void
65 on_channel_closed (TpProxy *proxy,
66     guint    domain,
67     gint     code,
68     gchar   *message,
69     EmpathyCallObserver *self)
70 {
71   DEBUG ("channel %s has been invalidated; stop observing it",
72       tp_proxy_get_object_path (proxy));
73
74   self->priv->channels = g_list_remove (self->priv->channels, proxy);
75   g_object_unref (proxy);
76 }
77
78 typedef struct
79 {
80   EmpathyCallObserver *self;
81   TpObserveChannelsContext *context;
82   TpChannel *main_channel;
83 } AutoRejectCtx;
84
85 static AutoRejectCtx *
86 auto_reject_ctx_new (EmpathyCallObserver *self,
87     TpObserveChannelsContext *context,
88     TpChannel *main_channel)
89 {
90   AutoRejectCtx *ctx = g_slice_new (AutoRejectCtx);
91
92   ctx->self = g_object_ref (self);
93   ctx->context = g_object_ref (context);
94   ctx->main_channel = g_object_ref (main_channel);
95   return ctx;
96 }
97
98 static void
99 auto_reject_ctx_free (AutoRejectCtx *ctx)
100 {
101   g_object_unref (ctx->self);
102   g_object_unref (ctx->context);
103   g_object_unref (ctx->main_channel);
104   g_slice_free (AutoRejectCtx, ctx);
105 }
106
107 static void
108 get_contact_cb (TpConnection *connection,
109     guint n_contacts,
110     TpContact * const *contacts,
111     guint n_failed,
112     const TpHandle *failed,
113     const GError *error,
114     gpointer user_data,
115     GObject *weak_object)
116 {
117   EmpathyCallObserver *self = (EmpathyCallObserver *) weak_object;
118   NotifyNotification *notification;
119   TpContact *contact;
120   gchar *summary, *body;
121   EmpathyContact *emp_contact;
122   GdkPixbuf *pixbuf;
123
124   if (n_contacts != 1)
125     {
126       DEBUG ("Failed to get TpContact; ignoring notification bubble");
127       return;
128     }
129
130   contact = contacts[0];
131
132   summary = g_strdup_printf (_("Missed call from %s"),
133       tp_contact_get_alias (contact));
134   body = g_strdup_printf (
135       _("%s just tried to call you, but you were in another call."),
136       tp_contact_get_alias (contact));
137
138   notification = notify_notification_new (summary, body, NULL);
139
140   emp_contact = empathy_contact_dup_from_tp_contact (contact);
141   pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
142       self->priv->notify_mgr, emp_contact, EMPATHY_IMAGE_AVATAR_DEFAULT);
143
144   if (pixbuf != NULL)
145     {
146       notify_notification_set_icon_from_pixbuf (notification, pixbuf);
147       g_object_unref (pixbuf);
148     }
149
150   notify_notification_show (notification, NULL);
151
152   g_object_unref (notification);
153   g_free (summary);
154   g_free (body);
155   g_object_unref (emp_contact);
156 }
157
158 static void
159 display_reject_notification (EmpathyCallObserver *self,
160     TpChannel *channel)
161 {
162   TpHandle handle;
163   TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
164       TP_CONTACT_FEATURE_AVATAR_DATA };
165
166   handle = tp_channel_get_handle (channel, NULL);
167
168   tp_connection_get_contacts_by_handle (tp_channel_borrow_connection (channel),
169       1, &handle, G_N_ELEMENTS (features), features, get_contact_cb,
170       g_object_ref (channel), g_object_unref, G_OBJECT (self));
171 }
172
173 static TpChannel *
174 find_main_channel (GList *channels)
175 {
176   GList *l;
177
178   for (l = channels; l != NULL; l = g_list_next (l))
179     {
180       TpChannel *channel = l->data;
181       GQuark channel_type;
182
183       if (tp_proxy_get_invalidated (channel) != NULL)
184         continue;
185
186       channel_type = tp_channel_get_channel_type_id (channel);
187
188       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
189           channel_type == TPY_IFACE_QUARK_CHANNEL_TYPE_CALL)
190         return channel;
191     }
192
193   return NULL;
194 }
195
196 static gboolean
197 has_ongoing_calls (EmpathyCallObserver *self)
198 {
199   GList *l;
200
201   for (l = self->priv->channels; l != NULL; l = l->next)
202     {
203       TpChannel *channel = TP_CHANNEL (l->data);
204       GQuark type = tp_channel_get_channel_type_id (channel);
205
206       /* Check that Call channels are not ended */
207       if (type == TPY_IFACE_QUARK_CHANNEL_TYPE_CALL &&
208           tpy_call_channel_get_state (TPY_CALL_CHANNEL (channel), NULL, NULL)
209                == TPY_CALL_STATE_ENDED)
210         continue;
211
212       return TRUE;
213     }
214
215   return FALSE;
216 }
217
218 static void
219 claim_and_leave_cb (GObject *source,
220     GAsyncResult *result,
221     gpointer user_data)
222 {
223   AutoRejectCtx *ctx = user_data;
224   GError *error = NULL;
225
226   if (!tp_channel_dispatch_operation_leave_channels_finish (
227         TP_CHANNEL_DISPATCH_OPERATION (source), result, &error))
228     {
229       DEBUG ("Failed to reject call: %s", error->message);
230
231       g_error_free (error);
232       goto out;
233     }
234
235   display_reject_notification (ctx->self, ctx->main_channel);
236
237 out:
238   auto_reject_ctx_free (ctx);
239 }
240
241 static void
242 observe_channels (TpSimpleObserver *observer,
243     TpAccount *account,
244     TpConnection *connection,
245     GList *channels,
246     TpChannelDispatchOperation *dispatch_operation,
247     GList *requests,
248     TpObserveChannelsContext *context,
249     gpointer user_data)
250 {
251   EmpathyCallObserver *self = EMPATHY_CALL_OBSERVER (user_data);
252   TpChannel *channel;
253   const GError *error;
254
255   channel = find_main_channel (channels);
256   if (channel == NULL)
257     {
258       GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
259           "Unknown channel type" };
260
261       DEBUG ("Didn't find any Call or StreamedMedia channel; ignoring");
262
263       tp_observe_channels_context_fail (context, &err);
264       return;
265     }
266
267   /* Autoreject if there are other ongoing calls */
268   if (has_ongoing_calls (self))
269     {
270       AutoRejectCtx *ctx = auto_reject_ctx_new (self, context, channel);
271
272       DEBUG ("Autorejecting incoming call since there are others in "
273           "progress: %s", tp_proxy_get_object_path (channel));
274
275       tp_channel_dispatch_operation_leave_channels_async (dispatch_operation,
276           TP_CHANNEL_GROUP_CHANGE_REASON_BUSY, "Already in a call",
277           claim_and_leave_cb, ctx);
278
279       tp_observe_channels_context_accept (context);
280       return;
281     }
282
283   if ((error = tp_proxy_get_invalidated (channel)) != NULL)
284     {
285       DEBUG ("The channel has already been invalidated: %s",
286           error->message);
287
288       tp_observe_channels_context_fail (context, error);
289       return;
290     }
291
292   DEBUG ("Observing channel %s", tp_proxy_get_object_path (channel));
293
294   tp_g_signal_connect_object (channel, "invalidated",
295       G_CALLBACK (on_channel_closed), self, 0);
296   self->priv->channels = g_list_prepend (self->priv->channels,
297       g_object_ref (channel));
298
299   tp_observe_channels_context_accept (context);
300 }
301
302 static GObject *
303 observer_constructor (GType type,
304     guint n_props,
305     GObjectConstructParam *props)
306 {
307   GObject *retval;
308
309   if (observer_singleton)
310     {
311       retval = g_object_ref (observer_singleton);
312     }
313   else
314     {
315       retval = G_OBJECT_CLASS (empathy_call_observer_parent_class)->constructor
316           (type, n_props, props);
317
318       observer_singleton = EMPATHY_CALL_OBSERVER (retval);
319       g_object_add_weak_pointer (retval, (gpointer) &observer_singleton);
320     }
321
322   return retval;
323 }
324
325 static void
326 observer_dispose (GObject *object)
327 {
328   EmpathyCallObserver *self = EMPATHY_CALL_OBSERVER (object);
329
330   tp_clear_object (&self->priv->notify_mgr);
331   tp_clear_object (&self->priv->observer);
332   g_list_free_full (self->priv->channels, g_object_unref);
333   self->priv->channels = NULL;
334 }
335
336 static void
337 empathy_call_observer_class_init (EmpathyCallObserverClass *klass)
338 {
339   GObjectClass *object_class = G_OBJECT_CLASS (klass);
340
341   object_class->dispose = observer_dispose;
342   object_class->constructor = observer_constructor;
343
344   g_type_class_add_private (object_class, sizeof (EmpathyCallObserverPriv));
345 }
346
347 static void
348 empathy_call_observer_init (EmpathyCallObserver *self)
349 {
350   EmpathyCallObserverPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
351     EMPATHY_TYPE_CALL_OBSERVER, EmpathyCallObserverPriv);
352   TpDBusDaemon *dbus;
353   EmpathyChannelFactory *factory;
354   GError *error = NULL;
355
356   self->priv = priv;
357
358   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
359
360   dbus = tp_dbus_daemon_dup (&error);
361   if (dbus == NULL)
362     {
363       DEBUG ("Failed to get TpDBusDaemon: %s", error->message);
364       g_error_free (error);
365       return;
366     }
367
368   self->priv->observer = tp_simple_observer_new (dbus, TRUE,
369       "Empathy.CallObserver", FALSE,
370       observe_channels, self, NULL);
371
372   factory = empathy_channel_factory_dup ();
373   tp_base_client_set_channel_factory (self->priv->observer,
374       TP_CLIENT_CHANNEL_FACTORY (factory));
375   g_object_unref (factory);
376
377   /* Observe Call and StreamedMedia channels */
378   tp_base_client_take_observer_filter (self->priv->observer,
379       tp_asv_new (
380         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
381           TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
382         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
383           TP_HANDLE_TYPE_CONTACT,
384         NULL));
385   tp_base_client_take_observer_filter (self->priv->observer,
386       tp_asv_new (
387         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
388           TPY_IFACE_CHANNEL_TYPE_CALL,
389         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
390           TP_HANDLE_TYPE_CONTACT,
391         NULL));
392
393   tp_base_client_set_observer_delay_approvers (self->priv->observer, TRUE);
394
395   if (!tp_base_client_register (self->priv->observer, &error))
396     {
397       DEBUG ("Failed to register observer: %s", error->message);
398       g_error_free (error);
399     }
400
401   g_object_unref (dbus);
402 }
403
404 EmpathyCallObserver *
405 empathy_call_observer_dup_singleton (void)
406 {
407   return g_object_new (EMPATHY_TYPE_CALL_OBSERVER, NULL);
408 }