diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/empathy-account-assistant.c | 4 | ||||
-rw-r--r-- | src/empathy-call-observer.c | 428 | ||||
-rw-r--r-- | src/empathy-call-observer.h | 54 | ||||
-rw-r--r-- | src/empathy-call-window.ui | 3 | ||||
-rw-r--r-- | src/empathy-debug-window.c | 2 | ||||
-rw-r--r-- | src/empathy-main-window.c | 6 | ||||
-rw-r--r-- | src/empathy-main-window.ui | 3 |
8 files changed, 498 insertions, 3 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 5dfa83838..cf3efb0b3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -154,6 +154,7 @@ empathy_handwritten_source = \ empathy-migrate-butterfly-logs.c empathy-migrate-butterfly-logs.h \ empathy-new-chatroom-dialog.c empathy-new-chatroom-dialog.h \ empathy-notifications-approver.c empathy-notifications-approver.h \ + empathy-call-observer.c empathy-call-observer.h \ empathy-preferences.c empathy-preferences.h \ empathy-status-icon.c empathy-status-icon.h \ empathy-chat-manager.c empathy-chat-manager.h \ diff --git a/src/empathy-account-assistant.c b/src/empathy-account-assistant.c index 2c89d6fc8..0f917fac5 100644 --- a/src/empathy-account-assistant.c +++ b/src/empathy-account-assistant.c @@ -1100,8 +1100,8 @@ account_assistant_build_salut_page (EmpathyAccountAssistant *self) g_object_unref (pix); - w = gtk_check_button_new_with_label ( - _("I don't want to enable this feature for now")); + w = gtk_check_button_new_with_mnemonic ( + _("I do _not want to enable this feature for now")); gtk_box_pack_start (GTK_BOX (main_vbox), w, FALSE, FALSE, 0); g_signal_connect (w, "toggled", G_CALLBACK (create_salut_check_box_toggled_cb), self); diff --git a/src/empathy-call-observer.c b/src/empathy-call-observer.c new file mode 100644 index 000000000..ee8adc406 --- /dev/null +++ b/src/empathy-call-observer.c @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> + */ + +#include <config.h> + +#include <glib/gi18n-lib.h> + +#include <telepathy-glib/telepathy-glib.h> + +#include <libnotify/notification.h> + +#include <libempathy/empathy-channel-factory.h> +#include <libempathy/empathy-utils.h> + +#include <libempathy-gtk/empathy-images.h> +#include <libempathy-gtk/empathy-notify-manager.h> + +#include <extensions/extensions.h> + +#include "empathy-call-observer.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_VOIP +#include <libempathy/empathy-debug.h> + +struct _EmpathyCallObserverPriv { + EmpathyNotifyManager *notify_mgr; + + TpBaseClient *observer; + + /* Ongoing calls, as reffed TpChannels */ + GList *channels; +}; + +/* The Call Observer looks at incoming and outgoing calls, and + * autorejects incoming ones if there are ongoing ones, since + * we don't cope with simultaneous calls quite well yet. + * At some point, we should ask the user if he wants to put the + * current call on hold and answer the incoming one instead, + * see https://bugzilla.gnome.org/show_bug.cgi?id=623348 + */ +G_DEFINE_TYPE (EmpathyCallObserver, empathy_call_observer, G_TYPE_OBJECT); + +static EmpathyCallObserver * observer_singleton = NULL; + +static void +on_channel_closed (TpProxy *proxy, + guint domain, + gint code, + gchar *message, + EmpathyCallObserver *self) +{ + DEBUG ("channel %s has been invalidated; stop observing it", + tp_proxy_get_object_path (proxy)); + + self->priv->channels = g_list_remove (self->priv->channels, proxy); + g_object_unref (proxy); +} + +typedef struct +{ + EmpathyCallObserver *self; + TpObserveChannelsContext *context; + TpChannel *main_channel; +} AutoRejectCtx; + +static AutoRejectCtx * +auto_reject_ctx_new (EmpathyCallObserver *self, + TpObserveChannelsContext *context, + TpChannel *main_channel) +{ + AutoRejectCtx *ctx = g_slice_new (AutoRejectCtx); + + ctx->self = g_object_ref (self); + ctx->context = g_object_ref (context); + ctx->main_channel = g_object_ref (main_channel); + return ctx; +} + +static void +auto_reject_ctx_free (AutoRejectCtx *ctx) +{ + g_object_unref (ctx->self); + g_object_unref (ctx->context); + g_object_unref (ctx->main_channel); + g_slice_free (AutoRejectCtx, ctx); +} + +static void +get_contact_cb (TpConnection *connection, + guint n_contacts, + TpContact * const *contacts, + guint n_failed, + const TpHandle *failed, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + EmpathyCallObserver *self = (EmpathyCallObserver *) weak_object; + NotifyNotification *notification; + TpContact *contact; + gchar *summary, *body; + EmpathyContact *emp_contact; + GdkPixbuf *pixbuf; + + if (n_contacts != 1) + { + DEBUG ("Failed to get TpContact; ignoring notification bubble"); + return; + } + + contact = contacts[0]; + + summary = g_strdup_printf (_("Missed call from %s"), + tp_contact_get_alias (contact)); + body = g_strdup_printf ( + _("%s just tried to call you, but you were in another call."), + tp_contact_get_alias (contact)); + + notification = notify_notification_new (summary, body, NULL); + + emp_contact = empathy_contact_dup_from_tp_contact (contact); + pixbuf = empathy_notify_manager_get_pixbuf_for_notification ( + self->priv->notify_mgr, emp_contact, EMPATHY_IMAGE_AVATAR_DEFAULT); + + if (pixbuf != NULL) + { + notify_notification_set_icon_from_pixbuf (notification, pixbuf); + g_object_unref (pixbuf); + } + + notify_notification_show (notification, NULL); + + g_object_unref (notification); + g_free (summary); + g_free (body); + g_object_unref (emp_contact); +} + +static void +display_reject_notification (EmpathyCallObserver *self, + TpChannel *channel) +{ + TpHandle handle; + TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS, + TP_CONTACT_FEATURE_AVATAR_DATA }; + + handle = tp_channel_get_handle (channel, NULL); + + tp_connection_get_contacts_by_handle (tp_channel_borrow_connection (channel), + 1, &handle, G_N_ELEMENTS (features), features, get_contact_cb, + g_object_ref (channel), g_object_unref, G_OBJECT (self)); +} + +static void +on_cdo_claim_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + AutoRejectCtx *ctx = user_data; + TpChannelDispatchOperation *cdo; + GError *error = NULL; + GPtrArray *channels; + guint i; + + cdo = TP_CHANNEL_DISPATCH_OPERATION (source_object); + + tp_channel_dispatch_operation_claim_finish (cdo, result, &error); + if (error != NULL) + { + DEBUG ("Could not claim CDO: %s", error->message); + g_error_free (error); + goto out; + } + + channels = tp_channel_dispatch_operation_borrow_channels (cdo); + for (i = 0; i < channels->len; i++) + { + TpChannel *channel = g_ptr_array_index (channels, i); + + tp_channel_leave_async (channel, + TP_CHANNEL_GROUP_CHANGE_REASON_BUSY, "Already in a call", + NULL, NULL); + } + + display_reject_notification (ctx->self, ctx->main_channel); + +out: + auto_reject_ctx_free (ctx); +} + +static void +cdo_prepare_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + AutoRejectCtx *ctx = user_data; + GError *error = NULL; + TpChannelDispatchOperation *cdo; + + cdo = TP_CHANNEL_DISPATCH_OPERATION (source_object); + + if (!tp_proxy_prepare_finish (source_object, result, &error)) + { + DEBUG ("Failed to prepare ChannelDispatchOperation: %s", error->message); + + tp_observe_channels_context_fail (ctx->context, error); + + g_error_free (error); + auto_reject_ctx_free (ctx); + return; + } + + tp_channel_dispatch_operation_claim_async (cdo, on_cdo_claim_cb, ctx); + + tp_observe_channels_context_accept (ctx->context); +} + +static TpChannel * +find_main_channel (GList *channels) +{ + GList *l; + + for (l = channels; l != NULL; l = g_list_next (l)) + { + TpChannel *channel = l->data; + GQuark channel_type; + + if (tp_proxy_get_invalidated (channel) != NULL) + continue; + + channel_type = tp_channel_get_channel_type_id (channel); + + if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA) + return channel; + } + + return NULL; +} + +static gboolean +has_ongoing_calls (EmpathyCallObserver *self) +{ + if (self->priv->channels != NULL) + return TRUE; + + return FALSE; +} + +static void +observe_channels (TpSimpleObserver *observer, + TpAccount *account, + TpConnection *connection, + GList *channels, + TpChannelDispatchOperation *dispatch_operation, + GList *requests, + TpObserveChannelsContext *context, + gpointer user_data) +{ + EmpathyCallObserver *self = EMPATHY_CALL_OBSERVER (user_data); + TpChannel *channel; + const GError *error; + + channel = find_main_channel (channels); + if (channel == NULL) + { + GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "Unknown channel type" }; + + DEBUG ("Didn't find any Call or StreamedMedia channel; ignoring"); + + tp_observe_channels_context_fail (context, &err); + return; + } + + /* Autoreject if there are other ongoing calls */ + if (has_ongoing_calls (self)) + { + AutoRejectCtx *ctx = auto_reject_ctx_new (self, context, channel); + GQuark features[] = { TP_CHANNEL_DISPATCH_OPERATION_FEATURE_CORE, 0 }; + + DEBUG ("Autorejecting incoming call since there are others in " + "progress: %s", tp_proxy_get_object_path (channel)); + + /* We have to prepare dispatch_operation so we'll be able to get the + * channels from it. */ + tp_proxy_prepare_async (dispatch_operation, features, + cdo_prepare_cb, ctx); + + tp_observe_channels_context_delay (context); + return; + } + + if ((error = tp_proxy_get_invalidated (channel)) != NULL) + { + DEBUG ("The channel has already been invalidated: %s", + error->message); + + tp_observe_channels_context_fail (context, error); + return; + } + + DEBUG ("Observing channel %s", tp_proxy_get_object_path (channel)); + + tp_g_signal_connect_object (channel, "invalidated", + G_CALLBACK (on_channel_closed), self, 0); + self->priv->channels = g_list_prepend (self->priv->channels, + g_object_ref (channel)); + + tp_observe_channels_context_accept (context); +} + +static GObject * +observer_constructor (GType type, + guint n_props, + GObjectConstructParam *props) +{ + GObject *retval; + + if (observer_singleton) + { + retval = g_object_ref (observer_singleton); + } + else + { + retval = G_OBJECT_CLASS (empathy_call_observer_parent_class)->constructor + (type, n_props, props); + + observer_singleton = EMPATHY_CALL_OBSERVER (retval); + g_object_add_weak_pointer (retval, (gpointer) &observer_singleton); + } + + return retval; +} + +static void +observer_dispose (GObject *object) +{ + EmpathyCallObserver *self = EMPATHY_CALL_OBSERVER (object); + + tp_clear_object (&self->priv->notify_mgr); + tp_clear_object (&self->priv->observer); + g_list_free_full (self->priv->channels, g_object_unref); + self->priv->channels = NULL; +} + +static void +empathy_call_observer_class_init (EmpathyCallObserverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = observer_dispose; + object_class->constructor = observer_constructor; + + g_type_class_add_private (object_class, sizeof (EmpathyCallObserverPriv)); +} + +static void +empathy_call_observer_init (EmpathyCallObserver *self) +{ + EmpathyCallObserverPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_CALL_OBSERVER, EmpathyCallObserverPriv); + TpDBusDaemon *dbus; + EmpathyChannelFactory *factory; + GError *error = NULL; + + self->priv = priv; + + self->priv->notify_mgr = empathy_notify_manager_dup_singleton (); + + dbus = tp_dbus_daemon_dup (&error); + if (dbus == NULL) + { + DEBUG ("Failed to get TpDBusDaemon: %s", error->message); + g_error_free (error); + return; + } + + self->priv->observer = tp_simple_observer_new (dbus, TRUE, + "Empathy.CallObserver", FALSE, + observe_channels, self, NULL); + + factory = empathy_channel_factory_dup (); + tp_base_client_set_channel_factory (self->priv->observer, + TP_CLIENT_CHANNEL_FACTORY (factory)); + g_object_unref (factory); + + /* Observe StreamedMedia channels */ + tp_base_client_take_observer_filter (self->priv->observer, + tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, + TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, + TP_HANDLE_TYPE_CONTACT, + NULL)); + + tp_base_client_set_observer_delay_approvers (self->priv->observer, TRUE); + + if (!tp_base_client_register (self->priv->observer, &error)) + { + DEBUG ("Failed to register observer: %s", error->message); + g_error_free (error); + } + + g_object_unref (dbus); +} + +EmpathyCallObserver * +empathy_call_observer_dup_singleton (void) +{ + return g_object_new (EMPATHY_TYPE_CALL_OBSERVER, NULL); +} diff --git a/src/empathy-call-observer.h b/src/empathy-call-observer.h new file mode 100644 index 000000000..1d5a58b57 --- /dev/null +++ b/src/empathy-call-observer.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> + */ + +#ifndef __EMPATHY_CALL_OBSERVER_H__ +#define __EMPATHY_CALL_OBSERVER_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_CALL_OBSERVER (empathy_call_observer_get_type ()) +#define EMPATHY_CALL_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CALL_OBSERVER, EmpathyCallObserver)) +#define EMPATHY_CALL_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_CALL_OBSERVER, EmpathyCallObserverClass)) +#define EMPATHY_IS_CALL_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CALL_OBSERVER)) +#define EMPATHY_IS_CALL_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CALL_OBSERVER)) +#define EMPATHY_CALL_OBSERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CALL_OBSERVER, EmpathyCallObserverClass)) + +typedef struct _EmpathyCallObserver EmpathyCallObserver; +typedef struct _EmpathyCallObserverPriv EmpathyCallObserverPriv; +typedef struct _EmpathyCallObserverClass EmpathyCallObserverClass; + +struct _EmpathyCallObserver { + GObject parent; + EmpathyCallObserverPriv *priv; +}; + +struct _EmpathyCallObserverClass { + GObjectClass parent_class; +}; + +GType empathy_call_observer_get_type (void) G_GNUC_CONST; +EmpathyCallObserver *empathy_call_observer_dup_singleton (void); + +G_END_DECLS + +#endif /* __EMPATHY_CALL_OBSERVER_H__ */ diff --git a/src/empathy-call-window.ui b/src/empathy-call-window.ui index 8b274a126..ecd00dfc1 100644 --- a/src/empathy-call-window.ui +++ b/src/empathy-call-window.ui @@ -110,6 +110,9 @@ <child> <object class="GtkToolbar" id="toolbar"> <property name="visible">True</property> + <style> + <class name="primary-toolbar"/> + </style> <child> <object class="GtkToolButton" id="hangup"> <property name="visible">True</property> diff --git a/src/empathy-debug-window.c b/src/empathy-debug-window.c index f09cbd5ff..97cda470c 100644 --- a/src/empathy-debug-window.c +++ b/src/empathy-debug-window.c @@ -1407,6 +1407,8 @@ am_prepared_cb (GObject *am, gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), TRUE); gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), + GTK_STYLE_CLASS_PRIMARY_TOOLBAR); gtk_widget_show (toolbar); gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0); diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c index 8c7642581..12b99ba5f 100644 --- a/src/empathy-main-window.c +++ b/src/empathy-main-window.c @@ -65,6 +65,7 @@ #include <libempathy-gtk/empathy-ui-utils.h> #include "empathy-accounts-dialog.h" +#include "empathy-call-observer.h" #include "empathy-chat-manager.h" #include "empathy-main-window.h" #include "empathy-preferences.h" @@ -108,7 +109,8 @@ struct _EmpathyMainWindowPriv { TpAccountManager *account_manager; EmpathyChatroomManager *chatroom_manager; EmpathyEventManager *event_manager; - EmpathySoundManager *sound_mgr;; + EmpathySoundManager *sound_mgr; + EmpathyCallObserver *call_observer; guint flash_timeout_id; gboolean flash_on; gboolean empty; @@ -874,6 +876,7 @@ empathy_main_window_finalize (GObject *window) g_signal_handlers_disconnect_by_func (priv->event_manager, main_window_event_removed_cb, window); + g_object_unref (priv->call_observer); g_object_unref (priv->event_manager); g_object_unref (priv->ui_manager); g_object_unref (priv->chatroom_manager); @@ -1945,6 +1948,7 @@ empathy_main_window_init (EmpathyMainWindow *window) empathy_geometry_bind (GTK_WINDOW (window), GEOMETRY_NAME); /* Enable event handling */ + priv->call_observer = empathy_call_observer_dup_singleton (); priv->event_manager = empathy_event_manager_dup_singleton (); g_signal_connect (priv->event_manager, "event-added", diff --git a/src/empathy-main-window.ui b/src/empathy-main-window.ui index b32878bdc..217b276f2 100644 --- a/src/empathy-main-window.ui +++ b/src/empathy-main-window.ui @@ -304,6 +304,9 @@ <object class="GtkToolbar" id="presence_toolbar"> <property name="visible">True</property> <property name="toolbar_style">GTK_TOOLBAR_BOTH</property> + <style> + <class name="primary-toolbar"/> + </style> </object> <packing> <property name="expand">False</property> |