summaryrefslogtreecommitdiff
path: root/gst-mixer/src/element.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst-mixer/src/element.c')
-rw-r--r--gst-mixer/src/element.c595
1 files changed, 595 insertions, 0 deletions
diff --git a/gst-mixer/src/element.c b/gst-mixer/src/element.c
new file mode 100644
index 0000000..a190582
--- /dev/null
+++ b/gst-mixer/src/element.c
@@ -0,0 +1,595 @@
+/* MATE Volume Control
+ * Copyright (C) 2003-2004 Ronald Bultje <[email protected]>
+ *
+ * element.c: widget representation of a single mixer element
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "element.h"
+#include "keys.h"
+#include "preferences.h"
+#include "track.h"
+#include "misc.h"
+#ifdef HAVE_SOUND_THEME
+#include "gvc-sound-theme-chooser.h"
+#endif
+
+G_DEFINE_TYPE (MateVolumeControlElement, mate_volume_control_element, GTK_TYPE_NOTEBOOK)
+
+static void mate_volume_control_element_class_init (MateVolumeControlElementClass *klass);
+static void mate_volume_control_element_init (MateVolumeControlElement *el);
+static void mate_volume_control_element_dispose (GObject *object);
+
+static void cb_mateconf (MateConfClient *client,
+ guint connection_id,
+ MateConfEntry *entry,
+ gpointer data);
+
+
+static void
+mate_volume_control_element_class_init (MateVolumeControlElementClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = mate_volume_control_element_dispose;
+}
+
+static void
+mate_volume_control_element_init (MateVolumeControlElement *el)
+{
+ el->client = NULL;
+ el->mixer = NULL;
+}
+
+GtkWidget *
+mate_volume_control_element_new (MateConfClient *client)
+{
+ MateVolumeControlElement *el;
+
+ /* element */
+ el = g_object_new (MATE_VOLUME_CONTROL_TYPE_ELEMENT, NULL);
+ el->client = g_object_ref (G_OBJECT (client));
+
+ mateconf_client_add_dir (el->client, MATE_VOLUME_CONTROL_KEY_DIR,
+ MATECONF_CLIENT_PRELOAD_RECURSIVE, NULL);
+ mateconf_client_notify_add (el->client, MATE_VOLUME_CONTROL_KEY_DIR,
+ cb_mateconf, el, NULL, NULL);
+
+ return GTK_WIDGET (el);
+}
+
+static void
+mate_volume_control_element_dispose (GObject *object)
+{
+ MateVolumeControlElement *el = MATE_VOLUME_CONTROL_ELEMENT (object);
+
+ if (el->client) {
+ g_object_unref (G_OBJECT (el->client));
+ el->client = NULL;
+ }
+
+ if (el->mixer) {
+ /* remove g_timeout_add() mainloop handlers */
+ mate_volume_control_element_change (el, NULL);
+ gst_element_set_state (GST_ELEMENT (el->mixer), GST_STATE_NULL);
+ gst_object_unref (GST_OBJECT (el->mixer));
+ el->mixer = NULL;
+ }
+
+ G_OBJECT_CLASS (mate_volume_control_element_parent_class)->dispose (object);
+}
+
+/*
+ * Checks if we want to show the track by default ("whitelist").
+ */
+
+gboolean
+mate_volume_control_element_whitelist (GstMixer *mixer,
+ GstMixerTrack *track)
+{
+ gint i, pos;
+ gboolean found = FALSE;
+
+ /* Yes this is a hack. */
+ static struct {
+ gchar *label;
+ gboolean done;
+ } list[] = {
+
+/* Translator comment: the names below are a whitelist for which volume
+ * controls to show by default. Make sure that those match the translations
+ * of GStreamer-plugins' ALSA/OSS plugins. */
+ { "cd", FALSE },
+ { "line", FALSE },
+ { "mic", FALSE },
+ { "pcm", FALSE },
+ { "headphone", FALSE },
+ { "speaker", FALSE },
+ { "volume", FALSE },
+ { "master", FALSE },
+ { "digital output", FALSE },
+ { "recording", FALSE },
+ { "front", FALSE },
+ { NULL, FALSE }
+ };
+
+ /*
+ * When the user changes devices, it is necessary to reset the whitelist
+ * to a good default state. This fixes bugs LP:345645, 576022
+ */
+ if (track == NULL)
+ {
+ for (i = 0; list[i].label != NULL; i++)
+ list[i].done = FALSE;
+ return TRUE;
+ }
+
+ /* honor the mixer supplied hints about whitelisting if available */
+ if (gst_mixer_get_mixer_flags (GST_MIXER (mixer)) & GST_MIXER_FLAG_HAS_WHITELIST) {
+ if (GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_WHITELIST)) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+ }
+
+ for (i = 0; !found && list[i].label != NULL; i++) {
+ gchar *label_l = NULL;
+
+ if (list[i].done)
+ continue;
+
+ /* make case insensitive */
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (track), "untranslated-label"))
+ g_object_get (track, "untranslated-label", &label_l, NULL);
+ if (label_l == NULL)
+ g_object_get (track, "label", &label_l, NULL);
+ for (pos = 0; label_l[pos] != '\0'; pos++)
+ label_l[pos] = g_ascii_tolower (label_l[pos]);
+
+ if (g_strrstr (label_l, list[i].label) != NULL) {
+ found = TRUE;
+ list[i].done = TRUE;
+ }
+ g_free (label_l);
+ }
+
+ return found;
+}
+
+/*
+ * Hide/show notebook page.
+ */
+
+static void
+update_tab_visibility (MateVolumeControlElement *el, gint page, gint tabnum)
+{
+ const GList *item;
+ gboolean visible = FALSE;
+ GtkWidget *t;
+
+ for (item = gst_mixer_list_tracks (el->mixer);
+ item != NULL; item = item->next) {
+ GstMixerTrack *track = item->data;
+ MateVolumeControlTrack *trkw =
+ g_object_get_data (G_OBJECT (track), "mate-volume-control-trkw");
+
+ if (get_page_num (el->mixer, track) == page && trkw->visible) {
+ visible = TRUE;
+ break;
+ }
+ }
+
+ t = gtk_notebook_get_nth_page (GTK_NOTEBOOK (el), tabnum);
+ if (visible)
+ gtk_widget_show (t);
+ else
+ gtk_widget_hide (t);
+}
+
+static void
+cb_notify_message (GstBus *bus, GstMessage *message, gpointer data)
+{
+ MateVolumeControlElement *el = data;
+ GstMixerMessageType type;
+ MateVolumeControlTrack *trkw;
+ GstMixerTrack *track = NULL;
+ GstMixerOptions *options = NULL;
+
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (el->mixer)) {
+ /* not from our mixer - can't update anything anyway */
+ return;
+ }
+
+ /* This code only calls refresh if the first_track changes, because the
+ * refresh code only retrieves the current value from that track anyway */
+ type = gst_mixer_message_get_type (message);
+ if (type == GST_MIXER_MESSAGE_MUTE_TOGGLED) {
+ gst_mixer_message_parse_mute_toggled (message, &track, NULL);
+ } else if (type == GST_MIXER_MESSAGE_VOLUME_CHANGED) {
+ gst_mixer_message_parse_volume_changed (message, &track, NULL, NULL);
+ } else if (type == GST_MIXER_MESSAGE_OPTION_CHANGED) {
+ gst_mixer_message_parse_option_changed (message, &options, NULL);
+ track = GST_MIXER_TRACK (options);
+ } else {
+ return;
+ }
+
+ trkw = g_object_get_data (G_OBJECT (track),
+ "mate-volume-control-trkw");
+
+ mate_volume_control_track_update (trkw);
+}
+
+/*
+ * Change the element. Basically recreates this object internally.
+ */
+
+void
+mate_volume_control_element_change (MateVolumeControlElement *el,
+ GstElement *element)
+{
+ struct {
+ GtkWidget *page, *old_sep, *new_sep, *flagbuttonbox;
+ gboolean use;
+ gint pos, height, width;
+ MateVolumeControlTrack * (* get_track_widget) (GtkTable *table,
+ gint tab_pos,
+ GstMixer *mixer,
+ GstMixerTrack *track,
+ GtkWidget *left_sep,
+ GtkWidget *right_sep,
+ GtkWidget *flagbox);
+
+ } content[4] = {
+ { NULL, NULL, NULL, NULL, FALSE, 0, 5, 1,
+ mate_volume_control_track_add_playback },
+ { NULL, NULL, NULL, NULL, FALSE, 0, 5, 1,
+ mate_volume_control_track_add_recording },
+ { NULL, NULL, NULL, NULL, FALSE, 0, 1, 3,
+ mate_volume_control_track_add_playback },
+ { NULL, NULL, NULL, NULL, FALSE, 0, 1, 3,
+ mate_volume_control_track_add_option }
+ };
+ static gboolean theme_page = FALSE;
+ const GList *item;
+ GstMixer *mixer;
+ GstBus *bus;
+ gint i;
+
+ /* remove old pages, but not the "Sound Theme" page */
+ i = 0;
+ if (theme_page)
+ i = 1;
+
+ while (gtk_notebook_get_n_pages (GTK_NOTEBOOK (el)) > i) {
+ gtk_notebook_remove_page (GTK_NOTEBOOK (el), 0);
+ }
+
+ /* take/put reference */
+ if (el->mixer) {
+ for (item = gst_mixer_list_tracks (el->mixer);
+ item != NULL; item = item->next) {
+ GstMixerTrack *track = item->data;
+ MateVolumeControlTrack *trkw;
+
+ trkw = g_object_get_data (G_OBJECT (track),
+ "mate-volume-control-trkw");
+ g_object_set_data (G_OBJECT (track), "mate-volume-control-trkw", NULL);
+ mate_volume_control_track_free (trkw);
+ }
+ }
+ if (!element)
+ return;
+
+ g_return_if_fail (GST_IS_MIXER (element));
+ mixer = GST_MIXER (element);
+ gst_object_replace ((GstObject **) &el->mixer, GST_OBJECT (element));
+
+ /* Bus for notifications */
+ if (GST_ELEMENT_BUS (mixer) == NULL) {
+ bus = gst_bus_new ();
+ gst_bus_add_signal_watch (bus);
+ g_signal_connect (G_OBJECT (bus), "message::element",
+ (GCallback) cb_notify_message, el);
+ gst_element_set_bus (GST_ELEMENT (mixer), bus);
+ }
+
+ /* content pages */
+ for (i = 0; i < 4; i++) {
+ content[i].page = gtk_table_new (content[i].width, content[i].height, FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (content[i].page), 6);
+ if (i >= 2)
+ gtk_table_set_row_spacings (GTK_TABLE (content[i].page), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (content[i].page), 6);
+ content[i].flagbuttonbox = NULL;
+ }
+
+ /* show */
+ mate_volume_control_element_whitelist (el->mixer, NULL);
+ for (item = gst_mixer_list_tracks (el->mixer);
+ item != NULL; item = item->next) {
+ GstMixerTrack *track = item->data;
+ MateVolumeControlTrack *trkw;
+ gchar *key;
+ const MateConfValue *value;
+ gboolean active;
+
+ i = get_page_num (el->mixer, track);
+
+ /* FIXME:
+ * - do not create separator if there is no more track
+ * _of this type_. We currently destroy it at the
+ * end, so it's not critical, but not nice either.
+ */
+ if (i == 3) {
+ content[i].new_sep = gtk_hseparator_new ();
+ } else if (i < 2) {
+ content[i].new_sep = gtk_vseparator_new ();
+ } else {
+ content[i].new_sep = NULL;
+ }
+
+ /* visible? */
+ active = mate_volume_control_element_whitelist (mixer, track);
+ key = get_mateconf_key (el->mixer, track);
+ if ((value = mateconf_client_get (el->client, key, NULL)) != NULL &&
+ value->type == MATECONF_VALUE_BOOL) {
+ active = mateconf_value_get_bool (value);
+ }
+ g_free (key);
+
+ /* Show left separator if we're not the first track */
+ if (active && content[i].use && content[i].old_sep) {
+
+ /* Do not show separator for switches/options on Playback/Recording tab */
+ if (i < 2 && track->num_channels != 0) {
+ gtk_widget_show (content[i].old_sep);
+ }
+ }
+
+ /* widget */
+ trkw = content[i].get_track_widget (GTK_TABLE (content[i].page),
+ content[i].pos++, el->mixer, track,
+ content[i].old_sep, content[i].new_sep,
+ content[i].flagbuttonbox);
+ mate_volume_control_track_show (trkw, active);
+
+ /* Only the first trkw on the page will return flagbuttonbox */
+ if (trkw->flagbuttonbox != NULL)
+ content[i].flagbuttonbox = trkw->flagbuttonbox;
+ g_object_set_data (G_OBJECT (track),
+ "mate-volume-control-trkw", trkw);
+
+ /* separator */
+ if (item->next != NULL && content[i].new_sep) {
+ if (i >= 2) {
+ gtk_table_attach (GTK_TABLE (content[i].page), content[i].new_sep,
+ 0, 3, content[i].pos, content[i].pos + 1,
+ GTK_SHRINK | GTK_FILL, 0, 0, 0);
+ } else {
+ gtk_table_attach (GTK_TABLE (content[i].page), content[i].new_sep,
+ content[i].pos, content[i].pos + 1, 0, 6,
+ 0, GTK_SHRINK | GTK_FILL, 0, 0);
+ }
+ content[i].pos++;
+ }
+
+ content[i].old_sep = content[i].new_sep;
+
+ if (active) {
+ content[i].use = TRUE;
+ }
+ }
+
+ /* show - need to build the tabs backwards so that deleting the "Sound Theme"
+ * page can be avoided.
+ */
+ for (i = 3; i >= 0; i--) {
+ GtkWidget *label, *view, *viewport;
+ GtkAdjustment *hadjustment, *vadjustment;
+
+ /* don't show last separator */
+ if (content[i].new_sep)
+ gtk_widget_destroy (content[i].new_sep);
+
+ /* viewport for lots of tracks */
+ view = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view),
+ i >= 2 ? GTK_POLICY_NEVER :
+ GTK_POLICY_AUTOMATIC,
+ i >= 2 ? GTK_POLICY_AUTOMATIC :
+ GTK_POLICY_NEVER);
+
+ hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (view));
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (view));
+ viewport = gtk_viewport_new (hadjustment, vadjustment);
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+
+ if (content[i].flagbuttonbox != NULL) {
+ GtkWidget *vbox = NULL;
+ GtkWidget *hbox = NULL;
+ GtkWidget *hbox2 = NULL;
+ GtkWidget *separator = NULL;
+
+ if (i < 2) {
+ vbox = gtk_vbox_new (FALSE, 0);
+ hbox = gtk_hbox_new (FALSE, 6);
+ hbox2 = gtk_hbox_new (FALSE, 6);
+ separator = gtk_hseparator_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), content[i].page, TRUE, TRUE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox2, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox2), separator, TRUE, TRUE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox), content[i].flagbuttonbox, TRUE,
+ FALSE, 6);
+ } else {
+ /* orientation is rotated for these ... */
+ vbox = gtk_hbox_new (FALSE, 0);
+ hbox = gtk_vbox_new (FALSE, 0);
+ hbox2 = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), content[i].page, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox2, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox), content[i].flagbuttonbox, TRUE,
+ FALSE, 6);
+ }
+ gtk_widget_show_all (hbox2);
+ gtk_widget_show (content[i].flagbuttonbox);
+ gtk_widget_show (hbox);
+ gtk_widget_show (content[i].page);
+ gtk_widget_show (vbox);
+
+ gtk_container_add (GTK_CONTAINER (viewport), vbox);
+ gtk_container_add (GTK_CONTAINER (view), viewport);
+ } else {
+ gtk_container_add (GTK_CONTAINER (viewport), content[i].page);
+ gtk_container_add (GTK_CONTAINER (view), viewport);
+ }
+
+ label = gtk_label_new (get_page_description (i));
+ gtk_notebook_prepend_page (GTK_NOTEBOOK (el), view, label);
+ gtk_widget_show (content[i].page);
+ gtk_widget_show (viewport);
+ gtk_widget_show (view);
+ gtk_widget_show (label);
+
+ update_tab_visibility (el, i, 0);
+ }
+
+ /* refresh fix */
+ for (i = gtk_notebook_get_n_pages (GTK_NOTEBOOK (el)) - 1;
+ i >= 0; i--) {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (el), i);
+ }
+
+#ifdef HAVE_SOUND_THEME
+ /* Add tab for managing themes */
+ if (!theme_page) {
+ theme_page = TRUE;
+ GtkWidget *label, *view, *viewport, *sound_theme_chooser, *vbox;
+ GtkAdjustment *hadjustment, *vadjustment;
+
+ label = gtk_label_new (_("Sound Theme"));
+
+ view = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (view));
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (view));
+ viewport = gtk_viewport_new (hadjustment, vadjustment);
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+
+ sound_theme_chooser = gvc_sound_theme_chooser_new ();
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), sound_theme_chooser, TRUE, TRUE, 6);
+ gtk_container_add (GTK_CONTAINER (viewport), vbox);
+ gtk_container_add (GTK_CONTAINER (view), viewport);
+
+ gtk_widget_show_all (vbox);
+ gtk_widget_show (sound_theme_chooser);
+ gtk_widget_show (viewport);
+ gtk_widget_show (view);
+ gtk_widget_show (label);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (el), view, label);
+ }
+#endif
+}
+
+/*
+ * MateConf callback.
+ */
+
+static void
+cb_mateconf (MateConfClient *client,
+ guint connection_id,
+ MateConfEntry *entry,
+ gpointer data)
+{
+ MateVolumeControlElement *el = MATE_VOLUME_CONTROL_ELEMENT (data);
+ gchar *keybase = get_mateconf_key (el->mixer, NULL);
+
+ if (!strncmp (mateconf_entry_get_key (entry),
+ keybase, strlen (keybase))) {
+ const GList *item;
+
+ for (item = gst_mixer_list_tracks (el->mixer);
+ item != NULL; item = item->next) {
+ GstMixerTrack *track = item->data;
+ MateVolumeControlTrack *trkw =
+ g_object_get_data (G_OBJECT (track), "mate-volume-control-trkw");
+ gchar *key = get_mateconf_key (el->mixer, track);
+
+ g_return_if_fail (mateconf_entry_get_key (entry) != NULL);
+ g_return_if_fail (key != NULL);
+
+ if (g_str_equal (mateconf_entry_get_key (entry), key)) {
+ MateConfValue *value = mateconf_entry_get_value (entry);
+
+ if (value->type == MATECONF_VALUE_BOOL) {
+ gboolean active = mateconf_value_get_bool (value),
+ first[4] = { TRUE, TRUE, TRUE, TRUE };
+ gint n, page = get_page_num (el->mixer, track);
+
+ mate_volume_control_track_show (trkw, active);
+
+ /* separators */
+ for (item = gst_mixer_list_tracks (el->mixer);
+ item != NULL; item = item->next) {
+ GstMixerTrack *track = item->data;
+ MateVolumeControlTrack *trkw =
+ g_object_get_data (G_OBJECT (track), "mate-volume-control-trkw");
+
+ n = get_page_num (el->mixer, track);
+ if (trkw->visible && !first[n]) {
+ if (trkw->left_separator) {
+ if (n < 2 && track->num_channels == 0) {
+ gtk_widget_hide (trkw->left_separator);
+ } else {
+ gtk_widget_show (trkw->left_separator);
+ }
+ }
+ } else {
+ if (trkw->left_separator)
+ gtk_widget_hide (trkw->left_separator);
+ }
+
+ if (trkw->visible && first[n])
+ first[n] = FALSE;
+ }
+ update_tab_visibility (el, page, page);
+ break;
+ }
+ }
+
+ g_free (key);
+ }
+ }
+ g_free (keybase);
+}