2 * Copyright (C) 2011 Collabora Ltd.
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.
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.
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
18 * Authors: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
23 #include <glib/gi18n-lib.h>
25 #include <telepathy-glib/telepathy-glib.h>
27 #include <telepathy-yell/telepathy-yell.h>
29 #include <libnotify/notification.h>
31 #include <libempathy/empathy-utils.h>
33 #include <libempathy-gtk/empathy-images.h>
34 #include <libempathy-gtk/empathy-notify-manager.h>
36 #include <extensions/extensions.h>
38 #include "empathy-call-observer.h"
40 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
41 #include <libempathy/empathy-debug.h>
43 struct _EmpathyCallObserverPriv
{
44 EmpathyNotifyManager
*notify_mgr
;
46 TpBaseClient
*observer
;
48 /* Ongoing calls, as reffed TpChannels */
52 /* The Call Observer looks at incoming and outgoing calls, and
53 * autorejects incoming ones if there are ongoing ones, since
54 * we don't cope with simultaneous calls quite well yet.
55 * At some point, we should ask the user if he wants to put the
56 * current call on hold and answer the incoming one instead,
57 * see https://bugzilla.gnome.org/show_bug.cgi?id=623348
59 G_DEFINE_TYPE (EmpathyCallObserver
, empathy_call_observer
, G_TYPE_OBJECT
);
61 static EmpathyCallObserver
* observer_singleton
= NULL
;
64 on_channel_closed (TpProxy
*proxy
,
68 EmpathyCallObserver
*self
)
70 DEBUG ("channel %s has been invalidated; stop observing it",
71 tp_proxy_get_object_path (proxy
));
73 self
->priv
->channels
= g_list_remove (self
->priv
->channels
, proxy
);
74 g_object_unref (proxy
);
79 EmpathyCallObserver
*self
;
80 TpObserveChannelsContext
*context
;
81 TpChannel
*main_channel
;
84 static AutoRejectCtx
*
85 auto_reject_ctx_new (EmpathyCallObserver
*self
,
86 TpObserveChannelsContext
*context
,
87 TpChannel
*main_channel
)
89 AutoRejectCtx
*ctx
= g_slice_new (AutoRejectCtx
);
91 ctx
->self
= g_object_ref (self
);
92 ctx
->context
= g_object_ref (context
);
93 ctx
->main_channel
= g_object_ref (main_channel
);
98 auto_reject_ctx_free (AutoRejectCtx
*ctx
)
100 g_object_unref (ctx
->self
);
101 g_object_unref (ctx
->context
);
102 g_object_unref (ctx
->main_channel
);
103 g_slice_free (AutoRejectCtx
, ctx
);
107 get_contact_cb (TpConnection
*connection
,
109 TpContact
* const *contacts
,
111 const TpHandle
*failed
,
114 GObject
*weak_object
)
116 EmpathyCallObserver
*self
= (EmpathyCallObserver
*) weak_object
;
117 NotifyNotification
*notification
;
119 gchar
*summary
, *body
;
120 EmpathyContact
*emp_contact
;
125 DEBUG ("Failed to get TpContact; ignoring notification bubble");
129 contact
= contacts
[0];
131 summary
= g_strdup_printf (_("Missed call from %s"),
132 tp_contact_get_alias (contact
));
133 body
= g_strdup_printf (
134 _("%s just tried to call you, but you were in another call."),
135 tp_contact_get_alias (contact
));
137 notification
= notify_notification_new (summary
, body
, NULL
);
139 emp_contact
= empathy_contact_dup_from_tp_contact (contact
);
140 pixbuf
= empathy_notify_manager_get_pixbuf_for_notification (
141 self
->priv
->notify_mgr
, emp_contact
, EMPATHY_IMAGE_AVATAR_DEFAULT
);
145 notify_notification_set_icon_from_pixbuf (notification
, pixbuf
);
146 g_object_unref (pixbuf
);
149 notify_notification_show (notification
, NULL
);
151 g_object_unref (notification
);
154 g_object_unref (emp_contact
);
158 display_reject_notification (EmpathyCallObserver
*self
,
162 TpContactFeature features
[] = { TP_CONTACT_FEATURE_ALIAS
,
163 TP_CONTACT_FEATURE_AVATAR_DATA
};
165 handle
= tp_channel_get_handle (channel
, NULL
);
167 tp_connection_get_contacts_by_handle (tp_channel_borrow_connection (channel
),
168 1, &handle
, G_N_ELEMENTS (features
), features
, get_contact_cb
,
169 g_object_ref (channel
), g_object_unref
, G_OBJECT (self
));
173 find_main_channel (GList
*channels
)
177 for (l
= channels
; l
!= NULL
; l
= g_list_next (l
))
179 TpChannel
*channel
= l
->data
;
182 if (tp_proxy_get_invalidated (channel
) != NULL
)
185 channel_type
= tp_channel_get_channel_type_id (channel
);
187 if (channel_type
== TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA
||
188 channel_type
== TPY_IFACE_QUARK_CHANNEL_TYPE_CALL
)
196 has_ongoing_calls (EmpathyCallObserver
*self
)
200 for (l
= self
->priv
->channels
; l
!= NULL
; l
= l
->next
)
202 TpChannel
*channel
= TP_CHANNEL (l
->data
);
203 GQuark type
= tp_channel_get_channel_type_id (channel
);
205 /* Check that Call channels are not ended */
206 if (type
== TPY_IFACE_QUARK_CHANNEL_TYPE_CALL
&&
207 tpy_call_channel_get_state (TPY_CALL_CHANNEL (channel
), NULL
, NULL
)
208 == TPY_CALL_STATE_ENDED
)
218 claim_and_leave_cb (GObject
*source
,
219 GAsyncResult
*result
,
222 AutoRejectCtx
*ctx
= user_data
;
223 GError
*error
= NULL
;
225 if (!tp_channel_dispatch_operation_leave_channels_finish (
226 TP_CHANNEL_DISPATCH_OPERATION (source
), result
, &error
))
228 DEBUG ("Failed to reject call: %s", error
->message
);
230 g_error_free (error
);
234 display_reject_notification (ctx
->self
, ctx
->main_channel
);
237 auto_reject_ctx_free (ctx
);
241 observe_channels (TpSimpleObserver
*observer
,
243 TpConnection
*connection
,
245 TpChannelDispatchOperation
*dispatch_operation
,
247 TpObserveChannelsContext
*context
,
250 EmpathyCallObserver
*self
= EMPATHY_CALL_OBSERVER (user_data
);
254 channel
= find_main_channel (channels
);
257 GError err
= { TP_ERRORS
, TP_ERROR_INVALID_ARGUMENT
,
258 "Unknown channel type" };
260 DEBUG ("Didn't find any Call or StreamedMedia channel; ignoring");
262 tp_observe_channels_context_fail (context
, &err
);
266 /* Autoreject if there are other ongoing calls */
267 if (has_ongoing_calls (self
))
269 AutoRejectCtx
*ctx
= auto_reject_ctx_new (self
, context
, channel
);
271 DEBUG ("Autorejecting incoming call since there are others in "
272 "progress: %s", tp_proxy_get_object_path (channel
));
274 tp_channel_dispatch_operation_leave_channels_async (dispatch_operation
,
275 TP_CHANNEL_GROUP_CHANGE_REASON_BUSY
, "Already in a call",
276 claim_and_leave_cb
, ctx
);
278 tp_observe_channels_context_accept (context
);
282 if ((error
= tp_proxy_get_invalidated (channel
)) != NULL
)
284 DEBUG ("The channel has already been invalidated: %s",
287 tp_observe_channels_context_fail (context
, error
);
291 DEBUG ("Observing channel %s", tp_proxy_get_object_path (channel
));
293 tp_g_signal_connect_object (channel
, "invalidated",
294 G_CALLBACK (on_channel_closed
), self
, 0);
295 self
->priv
->channels
= g_list_prepend (self
->priv
->channels
,
296 g_object_ref (channel
));
298 tp_observe_channels_context_accept (context
);
302 observer_constructor (GType type
,
304 GObjectConstructParam
*props
)
308 if (observer_singleton
)
310 retval
= g_object_ref (observer_singleton
);
314 retval
= G_OBJECT_CLASS (empathy_call_observer_parent_class
)->constructor
315 (type
, n_props
, props
);
317 observer_singleton
= EMPATHY_CALL_OBSERVER (retval
);
318 g_object_add_weak_pointer (retval
, (gpointer
) &observer_singleton
);
325 observer_dispose (GObject
*object
)
327 EmpathyCallObserver
*self
= EMPATHY_CALL_OBSERVER (object
);
329 tp_clear_object (&self
->priv
->notify_mgr
);
330 tp_clear_object (&self
->priv
->observer
);
331 g_list_free_full (self
->priv
->channels
, g_object_unref
);
332 self
->priv
->channels
= NULL
;
336 empathy_call_observer_class_init (EmpathyCallObserverClass
*klass
)
338 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
340 object_class
->dispose
= observer_dispose
;
341 object_class
->constructor
= observer_constructor
;
343 g_type_class_add_private (object_class
, sizeof (EmpathyCallObserverPriv
));
347 empathy_call_observer_init (EmpathyCallObserver
*self
)
349 EmpathyCallObserverPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
350 EMPATHY_TYPE_CALL_OBSERVER
, EmpathyCallObserverPriv
);
351 TpAccountManager
*am
;
352 GError
*error
= NULL
;
356 self
->priv
->notify_mgr
= empathy_notify_manager_dup_singleton ();
358 am
= tp_account_manager_dup ();
360 self
->priv
->observer
= tp_simple_observer_new_with_am (am
, TRUE
,
361 "Empathy.CallObserver", FALSE
,
362 observe_channels
, self
, NULL
);
364 /* Observe Call and StreamedMedia channels */
365 tp_base_client_take_observer_filter (self
->priv
->observer
,
367 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
368 TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA
,
369 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE
, G_TYPE_UINT
,
370 TP_HANDLE_TYPE_CONTACT
,
372 tp_base_client_take_observer_filter (self
->priv
->observer
,
374 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
375 TPY_IFACE_CHANNEL_TYPE_CALL
,
376 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE
, G_TYPE_UINT
,
377 TP_HANDLE_TYPE_CONTACT
,
380 tp_base_client_set_observer_delay_approvers (self
->priv
->observer
, TRUE
);
382 if (!tp_base_client_register (self
->priv
->observer
, &error
))
384 DEBUG ("Failed to register observer: %s", error
->message
);
385 g_error_free (error
);
391 EmpathyCallObserver
*
392 empathy_call_observer_dup_singleton (void)
394 return g_object_new (EMPATHY_TYPE_CALL_OBSERVER
, NULL
);