/* * 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 "config.h" #include "empathy-mic-menu.h" #include "empathy-mic-monitor.h" #define DEBUG_FLAG EMPATHY_DEBUG_VOIP #include "empathy-debug.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; EmpathyMicMonitor *mic_monitor; }; G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT); #define MONITOR_KEY "empathy-mic-menu-is-monitor" 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)) { 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; gboolean active; g_object_get (action, "value", &value, NULL); active = (value == (gint) current_mic); if (active) { priv->in_update = TRUE; gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); priv->in_update = FALSE; } /* If action is a monitor then don't show it in the UI, BUT do * display it regardless if it is the current device. This is so * we don't have a rubbish UI by showing monitor devices in * Empathy, but still show the correct device when someone plays * with pavucontrol. */ if (g_object_get_data (G_OBJECT (action), MONITOR_KEY) != NULL && !active) continue; 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, gboolean is_monitor) { 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); /* Set MONITOR_KEY on the action to non-NULL if it's a monitor * because we don't want to show monitors if we can help it. */ if (is_monitor) { g_object_set_data (G_OBJECT (action), MONITOR_KEY, GUINT_TO_POINTER (TRUE)); } 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 (EmpathyMicMonitor *monitor, guint source_idx, const gchar *name, const gchar *description, gboolean is_monitor, EmpathyMicMenu *self) { empathy_mic_menu_add_microphone (self, name, description, source_idx, is_monitor); empathy_mic_menu_update (self); } static void empathy_mic_menu_microphone_removed_cb (EmpathyMicMonitor *monitor, 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_list_microphones_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object); EmpathyMicMenu *self = user_data; GError *error = NULL; const GList *mics = NULL; mics = empathy_mic_monitor_list_microphones_finish (monitor, result, &error); if (error != NULL) { DEBUG ("Failed to get microphone list: %s", error->message); g_clear_error (&error); return; } for (; mics != NULL; mics = mics->next) { EmpathyMicrophone *mic = mics->data; empathy_mic_menu_add_microphone (self, mic->name, mic->description, mic->index, mic->is_monitor); } 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->mic_monitor = empathy_mic_monitor_new (); 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); priv->microphones = g_queue_new (); /* Don't bother with any of this if we don't support changing * microphone, so don't listen for microphone changes or enumerate * the available microphones. */ if (!empathy_audio_src_supports_changing_mic (audio)) return; tp_g_signal_connect_object (audio, "notify::microphone", G_CALLBACK (empathy_mic_menu_notify_microphone_cb), self, 0); tp_g_signal_connect_object (priv->mic_monitor, "microphone-added", G_CALLBACK (empathy_mic_menu_microphone_added_cb), self, 0); tp_g_signal_connect_object (priv->mic_monitor, "microphone-removed", G_CALLBACK (empathy_mic_menu_microphone_removed_cb), self, 0); empathy_mic_monitor_list_microphones_async (priv->mic_monitor, empathy_mic_menu_list_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; tp_clear_object (&priv->mic_monitor); 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); }