From a36214f589404b37c6125b0a3faa756f9e001cd6 Mon Sep 17 00:00:00 2001 From: Jonny Lamb Date: Thu, 28 Jul 2011 12:45:21 +0100 Subject: [PATCH] call-window: add a Microphone menu to change mic on the fly Yay! Sorry this commit is kind of big with the addition of mic-menu, perhaps I should have added it in pieces. Signed-off-by: Jonny Lamb --- src/Makefile.am | 4 +- src/empathy-call-window.c | 6 + src/empathy-call-window.ui | 16 ++ src/empathy-mic-menu.c | 392 +++++++++++++++++++++++++++++++++++++ src/empathy-mic-menu.h | 56 ++++++ 5 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 src/empathy-mic-menu.c create mode 100644 src/empathy-mic-menu.h diff --git a/src/Makefile.am b/src/Makefile.am index e6574077..c232652c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -168,7 +168,9 @@ empathy_call_SOURCES = \ empathy-video-widget.c \ empathy-video-widget.h \ ev-sidebar.c \ - ev-sidebar.h + ev-sidebar.h \ + empathy-mic-menu.c \ + empathy-mic-menu.h nodist_empathy_call_SOURCES = $(BUILT_SOURCES) diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c index 8407dc85..afd02522 100644 --- a/src/empathy-call-window.c +++ b/src/empathy-call-window.c @@ -60,6 +60,7 @@ #include "empathy-audio-sink.h" #include "empathy-video-src.h" #include "ev-sidebar.h" +#include "empathy-mic-menu.h" #define CONTENT_HBOX_BORDER_WIDTH 6 #define CONTENT_HBOX_SPACING 3 @@ -218,6 +219,8 @@ struct _EmpathyCallWindowPriv gboolean pipeline_playing; EmpathySoundManager *sound_mgr; + + EmpathyMicMenu *mic_menu; }; #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv) @@ -1154,6 +1157,7 @@ empathy_call_window_init (EmpathyCallWindow *self) g_object_unref (gui); priv->sound_mgr = empathy_sound_manager_dup_singleton (); + priv->mic_menu = empathy_mic_menu_new (self); empathy_call_window_show_hangup_button (self, TRUE); @@ -1684,6 +1688,8 @@ empathy_call_window_dispose (GObject *object) tp_clear_object (&priv->sound_mgr); + tp_clear_object (&priv->mic_menu); + G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object); } diff --git a/src/empathy-call-window.ui b/src/empathy-call-window.ui index bbcd9722..8c0d5777 100644 --- a/src/empathy-call-window.ui +++ b/src/empathy-call-window.ui @@ -23,6 +23,19 @@ _Sidebar + + + edit + _Edit + + + + + _Microphone + menumicrophone + gnome-stock-mic + + view @@ -61,6 +74,9 @@ + + + diff --git a/src/empathy-mic-menu.c b/src/empathy-mic-menu.c new file mode 100644 index 00000000..a2cc34cc --- /dev/null +++ b/src/empathy-mic-menu.c @@ -0,0 +1,392 @@ +/* + * 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 + * + * GtkAction code based on gnome-terminal's TerminalTabsMenu object. + * Thanks guys! + */ + +#include + +#include + +#include "empathy-mic-menu.h" + +struct _EmpathyMicMenuPrivate +{ + /* Borrowed ref; the call window actually owns us. */ + EmpathyCallWindow *window; + + /* Given away ref; the call window's UI manager now owns this. */ + GtkActionGroup *action_group; + + /* An invisible radio action so new microphones are always in the + * same radio group. */ + GtkAction *anchor_action; + + /* The merge ID used with the UI manager. We need to keep this + * around so in _clean we can remove all the items we've added + * before and start again. */ + guint ui_id; + + /* TRUE if we're in _update and so calling _set_active. */ + gboolean in_update; + + /* Queue of GtkRadioActions. */ + GQueue *microphones; +}; + +G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT); + +enum +{ + PROP_WINDOW = 1, +}; + +static void empathy_mic_menu_update (EmpathyMicMenu *self); + +static void +empathy_mic_menu_init (EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuPrivate); + + self->priv = priv; +} + +static void +empathy_mic_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (object); + EmpathyMicMenuPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_WINDOW: + priv->window = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +empathy_mic_menu_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (object); + EmpathyMicMenuPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_WINDOW: + g_value_set_object (value, priv->window); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +empathy_mic_menu_clean (EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GtkUIManager *ui_manager; + + if (priv->ui_id == 0) + return; + + ui_manager = empathy_call_window_get_ui_manager (priv->window); + + gtk_ui_manager_remove_ui (ui_manager, priv->ui_id); + gtk_ui_manager_ensure_update (ui_manager); + priv->ui_id = 0; +} + +static void +empathy_mic_menu_change_mic_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object); + EmpathyMicMenu *self = user_data; + GError *error = NULL; + + if (!empathy_audio_src_change_microphone_finish (audio, result, &error)) + { + g_debug ("Failed to change microphone: %s", error->message); + g_clear_error (&error); + + /* We call update here because if this change operation failed + * and we don't update the menu items, it'll point to the wrong + * device. We don't want to call it if the change was successful + * because we'll get the notify::microphone signal fired in a + * bit and the current value hasn't changed so it'd keep jumping + * between these values like there's no tomorrow, etc. */ + empathy_mic_menu_update (self); + } +} + +static void +empathy_mic_menu_activate_cb (GtkToggleAction *action, + EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + EmpathyGstAudioSrc *audio; + gint value; + + if (priv->in_update) + return; + + audio = empathy_call_window_get_audio_src (priv->window); + + g_object_get (action, "value", &value, NULL); + + empathy_audio_src_change_microphone_async (audio, value, + empathy_mic_menu_change_mic_cb, self); +} + +static void +empathy_mic_menu_update (EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GList *l; + GtkUIManager *ui_manager; + EmpathyGstAudioSrc *audio; + guint current_mic; + + ui_manager = empathy_call_window_get_ui_manager (priv->window); + + audio = empathy_call_window_get_audio_src (priv->window); + current_mic = empathy_audio_src_get_microphone (audio); + + empathy_mic_menu_clean (self); + priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager); + + for (l = priv->microphones->head; l != NULL; l = l->next) + { + GtkRadioAction *action = l->data; + const gchar *name = gtk_action_get_name (GTK_ACTION (action)); + gint value; + + g_object_get (action, "value", &value, NULL); + + if (value == (gint) current_mic) + { + priv->in_update = TRUE; + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + priv->in_update = FALSE; + } + + gtk_ui_manager_add_ui (ui_manager, priv->ui_id, + /* TODO: this should probably be passed from the call + * window, seeing that it's a reference to + * empathy-call-window.ui. */ + "/menubar1/edit/menumicrophone", + name, name, GTK_UI_MANAGER_MENUITEM, FALSE); + } +} + +static void +empathy_mic_menu_add_microphone (EmpathyMicMenu *self, + const gchar *name, + const gchar *description, + guint source_idx) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GtkRadioAction *action; + GSList *group; + + action = gtk_radio_action_new (name, description, NULL, NULL, source_idx); + gtk_action_group_add_action_with_accel (priv->action_group, + GTK_ACTION (action), NULL); + + group = gtk_radio_action_get_group (GTK_RADIO_ACTION (priv->anchor_action)); + gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group); + + g_queue_push_tail (priv->microphones, action); + + g_signal_connect (action, "activate", + G_CALLBACK (empathy_mic_menu_activate_cb), self); +} + +static void +empathy_mic_menu_notify_microphone_cb (EmpathyGstAudioSrc *audio, + GParamSpec *pspec, + EmpathyMicMenu *self) +{ + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio, + guint source_idx, + const gchar *name, + const gchar *description, + EmpathyMicMenu *self) +{ + empathy_mic_menu_add_microphone (self, name, description, source_idx); + + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio, + guint source_idx, + EmpathyMicMenu *self) +{ + EmpathyMicMenuPrivate *priv = self->priv; + GList *l; + + for (l = priv->microphones->head; l != NULL; l = l->next) + { + GtkRadioAction *action = l->data; + gint value; + + g_object_get (action, "value", &value, NULL); + + if (value != (gint) source_idx) + { + action = NULL; + continue; + } + + g_signal_handlers_disconnect_by_func (action, + G_CALLBACK (empathy_mic_menu_activate_cb), self); + + gtk_action_group_remove_action (priv->action_group, GTK_ACTION (action)); + g_queue_remove (priv->microphones, action); + break; + } + + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_get_microphones_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object); + EmpathyMicMenu *self = user_data; + GError *error = NULL; + const GList *mics = NULL; + + mics = empathy_audio_src_get_microphones_finish (audio, result, &error); + + if (error != NULL) + { + g_debug ("Failed to get microphone list: %s", error->message); + g_clear_error (&error); + return; + } + + for (; mics != NULL; mics = mics->next) + { + EmpathyAudioSrcMicrophone *mic = mics->data; + + empathy_mic_menu_add_microphone (self, mic->name, + mic->description, mic->index); + } + + empathy_mic_menu_update (self); +} + +static void +empathy_mic_menu_constructed (GObject *obj) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj); + EmpathyMicMenuPrivate *priv = self->priv; + GtkUIManager *ui_manager; + EmpathyGstAudioSrc *audio; + + g_assert (EMPATHY_IS_CALL_WINDOW (priv->window)); + + ui_manager = empathy_call_window_get_ui_manager (priv->window); + audio = empathy_call_window_get_audio_src (priv->window); + + g_assert (GTK_IS_UI_MANAGER (ui_manager)); + g_assert (EMPATHY_IS_GST_AUDIO_SRC (audio)); + + /* Okay let's go go go. */ + + priv->action_group = gtk_action_group_new ("EmpathyMicMenu"); + gtk_ui_manager_insert_action_group (ui_manager, priv->action_group, -1); + /* the UI manager now owns this */ + g_object_unref (priv->action_group); + + priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION, + "name", "EmpathyMicMenuAnchorAction", + NULL); + gtk_action_group_add_action (priv->action_group, priv->anchor_action); + g_object_unref (priv->anchor_action); + + tp_g_signal_connect_object (audio, "notify::microphone", + G_CALLBACK (empathy_mic_menu_notify_microphone_cb), self, 0); + tp_g_signal_connect_object (audio, "microphone-added", + G_CALLBACK (empathy_mic_menu_microphone_added_cb), self, 0); + tp_g_signal_connect_object (audio, "microphone-removed", + G_CALLBACK (empathy_mic_menu_microphone_removed_cb), self, 0); + + priv->microphones = g_queue_new (); + + empathy_audio_src_get_microphones_async (audio, + empathy_mic_menu_get_microphones_cb, self); +} + +static void +empathy_mic_menu_dispose (GObject *obj) +{ + EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj); + EmpathyMicMenuPrivate *priv = self->priv; + + if (priv->microphones != NULL) + g_queue_free (priv->microphones); + priv->microphones = NULL; + + G_OBJECT_CLASS (empathy_mic_menu_parent_class)->dispose (obj); +} + +static void +empathy_mic_menu_class_init (EmpathyMicMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = empathy_mic_menu_set_property; + object_class->get_property = empathy_mic_menu_get_property; + object_class->constructed = empathy_mic_menu_constructed; + object_class->dispose = empathy_mic_menu_dispose; + + g_object_class_install_property (object_class, PROP_WINDOW, + g_param_spec_object ("window", "window", "window", + EMPATHY_TYPE_CALL_WINDOW, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (object_class, sizeof (EmpathyMicMenuPrivate)); +} + +EmpathyMicMenu * +empathy_mic_menu_new (EmpathyCallWindow *window) +{ + return g_object_new (EMPATHY_TYPE_MIC_MENU, + "window", window, + NULL); +} diff --git a/src/empathy-mic-menu.h b/src/empathy-mic-menu.h new file mode 100644 index 00000000..87691ae6 --- /dev/null +++ b/src/empathy-mic-menu.h @@ -0,0 +1,56 @@ +/* + * 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 + */ + +#ifndef __EMPATHY_MIC_MENU_H__ +#define __EMPATHY_MIC_MENU_H__ + +#include + +#include "empathy-call-window.h" + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_MIC_MENU (empathy_mic_menu_get_type ()) +#define EMPATHY_MIC_MENU(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_MIC_MENU, EmpathyMicMenu)) +#define EMPATHY_MIC_MENU_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuClass)) +#define EMPATHY_IS_MIC_MENU(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_MIC_MENU)) +#define EMPATHY_IS_MIC_MENU_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_MIC_MENU)) +#define EMPATHY_MIC_MENU_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuClass)) + +typedef struct _EmpathyMicMenu EmpathyMicMenu; +typedef struct _EmpathyMicMenuPrivate EmpathyMicMenuPrivate; +typedef struct _EmpathyMicMenuClass EmpathyMicMenuClass; + +struct _EmpathyMicMenu +{ + GObject parent; + EmpathyMicMenuPrivate *priv; +}; + +struct _EmpathyMicMenuClass +{ + GObjectClass parent_class; +}; + +GType empathy_mic_menu_get_type (void) G_GNUC_CONST; + +EmpathyMicMenu * empathy_mic_menu_new (EmpathyCallWindow *window); + +G_END_DECLS + +#endif /* __EMPATHY_MIC_MENU_H__ */ -- 2.39.2