gnome-settings-daemon r664 - in trunk: . plugins/media-keys plugins/media-keys/actions plugins/media-keys/cut-n-paste



Author: hadess
Date: Thu Jan 15 22:11:25 2009
New Revision: 664
URL: http://svn.gnome.org/viewvc/gnome-settings-daemon?rev=664&view=rev

Log:
2009-01-15  Bastien Nocera  <hadess hadess net>

	* plugins/media-keys/cut-n-paste/*:
	Cut'n'paste code from the PulseAudio enabled code in
	gnome-media's gnome-volume-control

	* plugins/media-keys/actions/acme-volume-*.[ch]:
	Remove the old AcmeVolume code

	* plugins/media-keys/actions/acme.glade:
	* plugins/media-keys/actions/acme.h: Move to plugins/media-keys/

	* configure.ac: Tell config.h when PulseAudio support is disabled

	* plugins/media-keys/gsd-media-keys-manager.c (update_dialog),
	(on_stream_event_notify), (do_sound_action), (update_default_sink),
	(on_control_ready), (on_control_default_sink_changed), (do_action),
	(gsd_media_keys_manager_start), (gsd_media_keys_manager_stop):
	Use PulseAudio directly to change the volume. It will automatically
	change the volume of the default audio output for the machine
	(Closes: #567177)

	* plugins/media-keys/Makefile.am:
	* plugins/media-keys/actions/Makefile.am: Changes for the above



Added:
   trunk/plugins/media-keys/acme-eject.png   (props changed)
      - copied unchanged from r651, /trunk/plugins/media-keys/actions/acme-eject.png
   trunk/plugins/media-keys/acme.glade   (props changed)
      - copied unchanged from r651, /trunk/plugins/media-keys/actions/acme.glade
   trunk/plugins/media-keys/acme.h   (props changed)
      - copied unchanged from r651, /trunk/plugins/media-keys/actions/acme.h
   trunk/plugins/media-keys/cut-n-paste/
   trunk/plugins/media-keys/cut-n-paste/Makefile.am
   trunk/plugins/media-keys/cut-n-paste/gvc-channel-map.c
   trunk/plugins/media-keys/cut-n-paste/gvc-channel-map.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-control.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-control.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-event-role.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-event-role.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink-input.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink-input.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source-output.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source-output.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source.h
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c
   trunk/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h
Removed:
   trunk/plugins/media-keys/actions/
Modified:
   trunk/ChangeLog
   trunk/configure.ac
   trunk/plugins/media-keys/Makefile.am
   trunk/plugins/media-keys/gsd-media-keys-manager.c

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Thu Jan 15 22:11:25 2009
@@ -224,6 +224,8 @@
 dnl PulseAudio section
 dnl ==============================================
 
+
+have_pulse=false
 AC_ARG_ENABLE(pulse,
   AC_HELP_STRING([--disable-pulse],
                  [turn off PulseAudio support]),
@@ -235,104 +237,16 @@
        [WANT_PULSE=yes]) dnl Default value
 
 if test x$WANT_PULSE = xyes ; then
-       PKG_CHECK_MODULES(PULSE, libpulse >= 0.9.11,
-             AC_DEFINE(HAVE_PULSE, 1, [Define if PULSE sound server should be used]),
+       PA_REQUIRED_VERSION=0.9.12
+       PKG_CHECK_MODULES(PULSE, glib-2.0 libpulse >= $PA_REQUIRED_VERSION libpulse-mainloop-glib >= $PA_REQUIRED_VERSION,
+             [have_pulse=true, AC_DEFINE(HAVE_PULSE, 1, [Define if PULSE sound server should be used])],
              [have_pulse=false])
 fi
+AM_CONDITIONAL(HAVE_PULSE, test x"$have_pulse" = "xtrue")
 
 AC_SUBST(PULSE_CFLAGS)
 AC_SUBST(PULSE_LIBS)
 
-dnl ==============================================
-dnl GStreamer section
-dnl ==============================================
-GST_MAJORMINOR=auto
-
-AC_ARG_ENABLE(gstreamer,
-AC_HELP_STRING([--enable-gstreamer],[use gstreamer if available (and optionally specify a version)]),
-[case "${enableval}" in
- yes) ENABLE_GSTREAMER=yes ;;
- 0.10) ENABLE_GSTREAMER=yes && GST_MAJORMINOR=0.10 ;;
- no)  ENABLE_GSTREAMER=no ;;
- *) AC_MSG_ERROR([
-                  *** Bad value ${enableval} for --enable-gstreamer
-                  *** Please use one of the following:
-                  ***    --enable-gstreamer=0.10
-               ]) ;;
-esac],
-[ENABLE_GSTREAMER=yes]) dnl Default value
-
-have_gstreamer=no
-if test "x$ENABLE_GSTREAMER" = "xyes"; then
-   GST_REQS=0.10.1.2
-   PKGS="gstreamer-0.10 >= $GST_REQS gstreamer-plugins-base-0.10 >= $GST_REQS"
-
-   PKG_CHECK_MODULES(GST, $PKGS, have_gstreamer=yes,
-                     AC_MSG_RESULT([no]))
-
-   if test "x$have_gstreamer" = "xyes"; then
-      GST_LIBS="$GST_LIBS -lgstinterfaces-0.10 -lgstaudio-0.10"
-   fi
-else
-   AC_MSG_NOTICE([*** GStreamer support disabled ***])
-fi
-AM_CONDITIONAL(HAVE_GSTREAMER, test "x$have_gstreamer" = "xyes")
-AC_SUBST(GST_LIBS)
-AC_SUBST(GST_CFLAGS)
-
-dnl ==============================================
-dnl OSS section
-dnl ==============================================
-
-have_oss=no
-if test "x$have_gstreamer" != "xyes"; then
-  AC_TRY_COMPILE([
-		  #ifdef __NetBSD__
-		  #include <sys/param.h>
-		  #include <sys/sysctl.h>
-		  #include <soundcard.h>
-		  #else
-		  #include <sys/soundcard.h>
-		  #endif
-		  ],[
-		     int arg = SNDCTL_DSP_SETFRAGMENT;
-		     ],[
-			have_oss=yes
-			])
-  AC_MSG_CHECKING(for OSS audio support)
-  AC_MSG_RESULT($have_oss)
-fi
-
-AM_CONDITIONAL(HAVE_OSS, test x"$have_oss" = "xyes")
-
-dnl ==============================================
-dnl ALSA section
-dnl ==============================================
-
-have_alsa=no
-if test "x$have_gstreamer" != "xyes"; then
-  AC_ARG_ENABLE(alsa,
-		AC_HELP_STRING([--disable-alsa],
-			       [turn off ALSA audio support]),
-			       [case "${enableval}" in
-				yes) WANT_ALSA=yes ;;
-				no)  WANT_ALSA=no ;;
-				*) AC_MSG_ERROR(bad value ${enableval} for --disable-alsa) ;;
-			esac],
-			[WANT_ALSA=yes]) dnl Default value
-
-  if test x$WANT_ALSA = xyes ; then
-	  PKG_CHECK_MODULES(ALSA, alsa >= 0.9.0,
-			    have_alsa=yes,
-			    AC_MSG_RESULT([disabled]))
-  fi
-
-  AC_SUBST(ALSA_LIBS)
-  AC_SUBST(ALSA_CFLAGS)
-fi
-
-AM_CONDITIONAL(HAVE_ALSA, test x"$have_alsa" = "xyes")
-
 # ---------------------------------------------------------------------------
 # Enable Profiling
 # ---------------------------------------------------------------------------
@@ -430,7 +344,7 @@
 plugins/keybindings/Makefile
 plugins/keyboard/Makefile
 plugins/media-keys/Makefile
-plugins/media-keys/actions/Makefile
+plugins/media-keys/cut-n-paste/Makefile
 plugins/mouse/Makefile
 plugins/screensaver/Makefile
 plugins/sound/Makefile
@@ -471,9 +385,7 @@
 
         dbus-1 system.d dir:      ${DBUS_SYS_DIR}
 
-        OSS support:              ${have_oss}
-        ALSA support:             ${have_alsa}
-        GStreamer support:        ${have_gstreamer}
+        PulseAudio support:       ${have_pulse}
 
         Profiling support:        ${enable_profiling}
 "

Modified: trunk/plugins/media-keys/Makefile.am
==============================================================================
--- trunk/plugins/media-keys/Makefile.am	(original)
+++ trunk/plugins/media-keys/Makefile.am	Thu Jan 15 22:11:25 2009
@@ -1,9 +1,12 @@
 NULL =
 
-SUBDIRS = actions
+SUBDIRS =
+plugin_LTLIBRARIES =
 
-plugin_LTLIBRARIES = \
-	libmedia-keys.la
+if HAVE_PULSE
+SUBDIRS += cut-n-paste
+plugin_LTLIBRARIES = libmedia-keys.la
+endif
 
 BUILT_SOURCES = 			\
 	gsd-media-keys-manager-glue.h	\
@@ -29,13 +32,14 @@
 	gsd-media-keys-manager.c	\
 	gsd-media-keys-window.h		\
 	gsd-media-keys-window.c		\
+	acme.h				\
 	$(BUILT_SOURCES)		\
 	$(NULL)
 
 libmedia_keys_la_CPPFLAGS = \
 	-I$(top_srcdir)/gnome-settings-daemon			\
 	-I$(top_srcdir)/plugins/common				\
-	-I$(top_builddir)/plugins/media-keys/actions		\
+	-I$(top_builddir)/plugins/media-keys/cut-n-paste	\
 	-DPIXMAPDIR=\""$(pkgdatadir)"\"				\
 	-DGLADEDIR=\""$(pkgdatadir)"\"				\
 	-DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\"	\
@@ -49,11 +53,10 @@
 	$(GSD_PLUGIN_LDFLAGS)
 
 libmedia_keys_la_LIBADD  = 		\
-	$(top_builddir)/plugins/common/libcommon.la		\
-	$(top_builddir)/plugins/media-keys/actions/libacme.la	\
-	$(SETTINGS_PLUGIN_LIBS)		\
-	$(XF86MISC_LIBS)		\
-	$(GST_LIBS)
+	$(top_builddir)/plugins/common/libcommon.la			\
+	$(top_builddir)/plugins/media-keys/cut-n-paste/libgvc.la	\
+	$(SETTINGS_PLUGIN_LIBS)						\
+	$(XF86MISC_LIBS)
 
 plugin_in_files = 		\
 	media-keys.gnome-settings-plugin.in
@@ -73,7 +76,7 @@
 
 test_media_window_CPPFLAGS = \
 	-I$(top_srcdir)/gnome-settings-daemon			\
-	-I$(top_builddir)/plugins/media-keys/actions		\
+	-I$(top_builddir)/plugins/media-keys/cut-n-paste	\
 	-DPIXMAPDIR=\""$(pkgdatadir)"\"				\
 	-DGLADEDIR=\""$(pkgdatadir)"\"				\
 	-DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\"	\
@@ -101,7 +104,7 @@
 test_media_keys_CPPFLAGS = \
 	-I$(top_srcdir)/gnome-settings-daemon			\
 	-I$(top_srcdir)/plugins/common				\
-	-I$(top_builddir)/plugins/media-keys/actions		\
+	-I$(top_builddir)/plugins/media-keys/cut-n-paste	\
 	-DPIXMAPDIR=\""$(pkgdatadir)"\"				\
 	-DGLADEDIR=\""$(pkgdatadir)"\"				\
 	-DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\"	\
@@ -112,18 +115,32 @@
 	$(AM_CFLAGS)
 
 test_media_keys_LDADD = \
-	$(top_builddir)/gnome-settings-daemon/libgsd-profile.la	\
-	$(top_builddir)/plugins/common/libcommon.la		\
-	$(top_builddir)/plugins/media-keys/actions/libacme.la	\
+	$(top_builddir)/gnome-settings-daemon/libgsd-profile.la		\
+	$(top_builddir)/plugins/common/libcommon.la			\
+	$(top_builddir)/plugins/media-keys/cut-n-paste/libgvc.la	\
 	$(SETTINGS_DAEMON_LIBS)			\
 	$(SETTINGS_PLUGIN_LIBS)			\
 	$(XF86MISC_LIBS)			\
 	$(GST_LIBS)
 
+gladedir = $(pkgdatadir)
+glade_DATA = 		\
+	acme.glade	\
+	$(NULL)
+
+pixmapsdir = $(pkgdatadir)
+pixmaps_DATA = 		\
+	acme-eject.png	\
+	$(NULL)
+
+DIST_SUBDIRS = cut-n-paste
+
 EXTRA_DIST = 				\
 	gsd-media-keys-manager.xml	\
 	gsd-marshal.list		\
-	$(plugin_in_files)
+	$(plugin_in_files)		\
+	$(glade_DATA)			\
+	$(pixmaps_DATA)
 
 CLEANFILES = 			\
 	$(BUILT_SOURCES)	\

Added: trunk/plugins/media-keys/cut-n-paste/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/Makefile.am	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,37 @@
+NULL =
+
+noinst_LTLIBRARIES = libgvc.la
+
+INCLUDES =					\
+	$(WARN_CFLAGS)				\
+	$(VOLUME_CONTROL_CFLAGS)		\
+	$(PULSE_CFLAGS)				\
+	$(NULL)
+
+libgvc_la_LIBADD =		\
+	$(VOLUME_CONTROL_LIBS)	\
+	$(PULSE_LIBS)		\
+	$(NULL)
+
+libgvc_la_SOURCES =			\
+	gvc-mixer-stream.h		\
+	gvc-mixer-stream.c		\
+	gvc-channel-map.h		\
+	gvc-channel-map.c		\
+	gvc-mixer-sink.h		\
+	gvc-mixer-sink.c		\
+	gvc-mixer-source.h		\
+	gvc-mixer-source.c		\
+	gvc-mixer-sink-input.h		\
+	gvc-mixer-sink-input.c		\
+	gvc-mixer-source-output.h	\
+	gvc-mixer-source-output.c	\
+	gvc-mixer-event-role.h		\
+	gvc-mixer-event-role.c		\
+	gvc-mixer-control.h		\
+	gvc-mixer-control.c		\
+	$(NULL)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        Makefile.in

Added: trunk/plugins/media-keys/cut-n-paste/gvc-channel-map.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-channel-map.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,154 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-channel-map.h"
+
+#define GVC_CHANNEL_MAP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapPrivate))
+
+struct GvcChannelMapPrivate
+{
+        guint                 num_channels;
+        pa_channel_position_t positions[PA_CHANNELS_MAX];
+        gdouble               gains[PA_CHANNELS_MAX];
+};
+
+enum {
+        GAINS_CHANGED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     gvc_channel_map_class_init (GvcChannelMapClass *klass);
+static void     gvc_channel_map_init       (GvcChannelMap      *channel_map);
+static void     gvc_channel_map_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcChannelMap, gvc_channel_map, G_TYPE_OBJECT)
+
+guint
+gvc_channel_map_get_num_channels (GvcChannelMap *map)
+{
+        g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), 0);
+        return map->priv->num_channels;
+}
+
+gdouble *
+gvc_channel_map_get_gains (GvcChannelMap *map)
+{
+        g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL);
+        return map->priv->gains;
+}
+
+pa_channel_position_t *
+gvc_channel_map_get_positions (GvcChannelMap *map)
+{
+        g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL);
+        return map->priv->positions;
+}
+
+static void
+gvc_channel_map_class_init (GvcChannelMapClass *klass)
+{
+        GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
+
+        gobject_class->finalize = gvc_channel_map_finalize;
+
+        signals [GAINS_CHANGED] =
+                g_signal_new ("gains-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcChannelMapClass, gains_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+
+        g_type_class_add_private (klass, sizeof (GvcChannelMapPrivate));
+}
+
+void
+gvc_channel_map_gains_changed (GvcChannelMap *map)
+{
+        g_return_if_fail (GVC_IS_CHANNEL_MAP (map));
+        g_signal_emit (map, signals[GAINS_CHANGED], 0);
+}
+
+static void
+gvc_channel_map_init (GvcChannelMap *map)
+{
+        map->priv = GVC_CHANNEL_MAP_GET_PRIVATE (map);
+}
+
+static void
+gvc_channel_map_finalize (GObject *object)
+{
+        GvcChannelMap *channel_map;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_CHANNEL_MAP (object));
+
+        channel_map = GVC_CHANNEL_MAP (object);
+
+        g_return_if_fail (channel_map->priv != NULL);
+
+        G_OBJECT_CLASS (gvc_channel_map_parent_class)->finalize (object);
+}
+
+GvcChannelMap *
+gvc_channel_map_new (void)
+{
+        GObject *map;
+        map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL);
+        return GVC_CHANNEL_MAP (map);
+}
+
+static void
+set_from_pa_map (GvcChannelMap        *map,
+                 const pa_channel_map *pa_map)
+{
+        guint i;
+
+        map->priv->num_channels = pa_map->channels;
+        for (i = 0; i < pa_map->channels; i++) {
+                map->priv->positions[i] = pa_map->map[i];
+                map->priv->gains[i] = 1.0;
+        }
+}
+
+GvcChannelMap *
+gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *pa_map)
+{
+        GObject *map;
+        map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL);
+
+        set_from_pa_map (GVC_CHANNEL_MAP (map), pa_map);
+
+        return GVC_CHANNEL_MAP (map);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-channel-map.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-channel-map.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_CHANNEL_MAP_H
+#define __GVC_CHANNEL_MAP_H
+
+#include <glib-object.h>
+#include <pulse/pulseaudio.h>
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_CHANNEL_MAP         (gvc_channel_map_get_type ())
+#define GVC_CHANNEL_MAP(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMap))
+#define GVC_CHANNEL_MAP_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass))
+#define GVC_IS_CHANNEL_MAP(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_CHANNEL_MAP))
+#define GVC_IS_CHANNEL_MAP_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_CHANNEL_MAP))
+#define GVC_CHANNEL_MAP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass))
+
+typedef struct GvcChannelMapPrivate GvcChannelMapPrivate;
+
+typedef struct
+{
+        GObject               parent;
+        GvcChannelMapPrivate *priv;
+} GvcChannelMap;
+
+typedef struct
+{
+        GObjectClass           parent_class;
+        void (*gains_changed) (GvcChannelMap *channel_map);
+} GvcChannelMapClass;
+
+GType                   gvc_channel_map_get_type                (void);
+
+GvcChannelMap *         gvc_channel_map_new                     (void);
+GvcChannelMap *         gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *map);
+guint                   gvc_channel_map_get_num_channels        (GvcChannelMap  *map);
+pa_channel_position_t * gvc_channel_map_get_positions           (GvcChannelMap  *map);
+gdouble *               gvc_channel_map_get_gains               (GvcChannelMap  *map);
+
+void                    gvc_channel_map_gains_changed           (GvcChannelMap  *map);
+
+G_END_DECLS
+
+#endif /* __GVC_CHANNEL_MAP_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-control.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-control.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,1589 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006-2008 Lennart Poettering
+ * Copyright (C) 2008 Sjoerd Simons <sjoerd luon net>
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/glib-mainloop.h>
+#include <pulse/ext-stream-restore.h>
+
+#include "gvc-mixer-control.h"
+#include "gvc-mixer-sink.h"
+#include "gvc-mixer-source.h"
+#include "gvc-mixer-sink-input.h"
+#include "gvc-mixer-source-output.h"
+#include "gvc-mixer-event-role.h"
+
+#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate))
+
+struct GvcMixerControlPrivate
+{
+        pa_glib_mainloop *pa_mainloop;
+        pa_mainloop_api  *pa_api;
+        pa_context       *pa_context;
+        int               n_outstanding;
+
+        gboolean          default_sink_is_set;
+        guint             default_sink_id;
+        char             *default_sink_name;
+        gboolean          default_source_is_set;
+        guint             default_source_id;
+        char             *default_source_name;
+
+        gboolean          event_sink_input_is_set;
+        guint             event_sink_input_id;
+
+        GHashTable       *all_streams;
+        GHashTable       *sinks; /* fixed outputs */
+        GHashTable       *sources; /* fixed inputs */
+        GHashTable       *sink_inputs; /* routable output streams */
+        GHashTable       *source_outputs; /* routable input streams */
+        GHashTable       *clients;
+};
+
+enum {
+        READY,
+        STREAM_ADDED,
+        STREAM_REMOVED,
+        DEFAULT_SINK_CHANGED,
+        DEFAULT_SOURCE_CHANGED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     gvc_mixer_control_class_init (GvcMixerControlClass *klass);
+static void     gvc_mixer_control_init       (GvcMixerControl      *mixer_control);
+static void     gvc_mixer_control_finalize   (GObject              *object);
+
+G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT)
+
+pa_context *
+gvc_mixer_control_get_pa_context (GvcMixerControl *control)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+        return control->priv->pa_context;
+}
+
+GvcMixerStream *
+gvc_mixer_control_get_event_sink_input (GvcMixerControl *control)
+{
+        GvcMixerStream *stream;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        stream = g_hash_table_lookup (control->priv->all_streams,
+                                      GUINT_TO_POINTER (control->priv->event_sink_input_id));
+
+        return stream;
+}
+
+gboolean
+gvc_mixer_control_set_default_sink (GvcMixerControl *control,
+                                    GvcMixerStream  *stream)
+{
+        pa_operation *o;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        o = pa_context_set_default_sink (control->priv->pa_context,
+                                         gvc_mixer_stream_get_name (stream),
+                                         NULL,
+                                         NULL);
+        if (o == NULL) {
+                g_warning ("pa_context_set_default_sink() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref (o);
+
+        return TRUE;
+}
+
+gboolean
+gvc_mixer_control_set_default_source (GvcMixerControl *control,
+                                      GvcMixerStream  *stream)
+{
+        pa_operation *o;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        o = pa_context_set_default_source (control->priv->pa_context,
+                                           gvc_mixer_stream_get_name (stream),
+                                           NULL,
+                                           NULL);
+        if (o == NULL) {
+                g_warning ("pa_context_set_default_source() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref (o);
+
+        return TRUE;
+}
+
+GvcMixerStream *
+gvc_mixer_control_get_default_sink (GvcMixerControl *control)
+{
+        GvcMixerStream *stream;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        if (control->priv->default_sink_is_set) {
+                stream = g_hash_table_lookup (control->priv->all_streams,
+                                              GUINT_TO_POINTER (control->priv->default_sink_id));
+        } else {
+                stream = NULL;
+        }
+
+        return stream;
+}
+
+GvcMixerStream *
+gvc_mixer_control_get_default_source (GvcMixerControl *control)
+{
+        GvcMixerStream *stream;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        if (control->priv->default_source_is_set) {
+                stream = g_hash_table_lookup (control->priv->all_streams,
+                                              GUINT_TO_POINTER (control->priv->default_source_id));
+        } else {
+                stream = NULL;
+        }
+
+        return stream;
+}
+
+GvcMixerStream *
+gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
+                                    guint            id)
+{
+        GvcMixerStream *stream;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        stream = g_hash_table_lookup (control->priv->all_streams,
+                                      GUINT_TO_POINTER (id));
+        return stream;
+}
+
+static void
+listify_hash_values_hfunc (gpointer key,
+                           gpointer value,
+                           gpointer user_data)
+{
+        GSList **list = user_data;
+
+        *list = g_slist_prepend (*list, value);
+}
+
+static int
+gvc_stream_collate (GvcMixerStream *a,
+                    GvcMixerStream *b)
+{
+        const char *namea;
+        const char *nameb;
+
+        g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0);
+        g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0);
+
+        namea = gvc_mixer_stream_get_name (a);
+        nameb = gvc_mixer_stream_get_name (b);
+
+        return g_utf8_collate (namea, nameb);
+}
+
+GSList *
+gvc_mixer_control_get_streams (GvcMixerControl *control)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (control->priv->all_streams,
+                              listify_hash_values_hfunc,
+                              &retval);
+        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
+}
+
+GSList *
+gvc_mixer_control_get_sinks (GvcMixerControl *control)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (control->priv->sinks,
+                              listify_hash_values_hfunc,
+                              &retval);
+        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
+}
+
+GSList *
+gvc_mixer_control_get_sources (GvcMixerControl *control)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (control->priv->sources,
+                              listify_hash_values_hfunc,
+                              &retval);
+        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
+}
+
+GSList *
+gvc_mixer_control_get_sink_inputs (GvcMixerControl *control)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (control->priv->sink_inputs,
+                              listify_hash_values_hfunc,
+                              &retval);
+        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
+}
+
+GSList *
+gvc_mixer_control_get_source_outputs (GvcMixerControl *control)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (control->priv->source_outputs,
+                              listify_hash_values_hfunc,
+                              &retval);
+        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
+}
+
+static void
+dec_outstanding (GvcMixerControl *control)
+{
+        if (control->priv->n_outstanding <= 0) {
+                return;
+        }
+
+        if (--control->priv->n_outstanding <= 0) {
+                g_signal_emit (G_OBJECT (control), signals[READY], 0);
+        }
+}
+
+gboolean
+gvc_mixer_control_is_ready (GvcMixerControl *control)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
+
+        return (control->priv->n_outstanding == 0);
+}
+
+
+static void
+_set_default_source (GvcMixerControl *control,
+                     GvcMixerStream  *stream)
+{
+        guint new_id;
+
+        new_id = 0;
+
+        if (stream != NULL) {
+                new_id = gvc_mixer_stream_get_id (stream);
+        }
+
+        if (control->priv->default_source_id != new_id) {
+                control->priv->default_source_id = new_id;
+                control->priv->default_source_is_set = TRUE;
+                g_signal_emit (control,
+                               signals[DEFAULT_SOURCE_CHANGED],
+                               0,
+                               new_id);
+        }
+}
+
+static void
+_set_default_sink (GvcMixerControl *control,
+                   GvcMixerStream  *stream)
+{
+        guint new_id;
+
+        new_id = 0;
+
+        if (stream != NULL) {
+                new_id = gvc_mixer_stream_get_id (stream);
+        }
+
+        if (control->priv->default_sink_id != new_id) {
+                control->priv->default_sink_id = new_id;
+                control->priv->default_sink_is_set = TRUE;
+
+                g_signal_emit (control,
+                               signals[DEFAULT_SINK_CHANGED],
+                               0,
+                               new_id);
+        }
+}
+
+static gboolean
+_stream_has_name (gpointer        key,
+                  GvcMixerStream *stream,
+                  const char     *name)
+{
+        const char *t_name;
+
+        t_name = gvc_mixer_stream_get_name (stream);
+
+        if (t_name != NULL
+            && name != NULL
+            && strcmp (t_name, name) == 0) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static GvcMixerStream  *
+find_stream_for_name (GvcMixerControl *control,
+                      const char      *name)
+{
+        GvcMixerStream *stream;
+
+        stream = g_hash_table_find (control->priv->all_streams,
+                                    (GHRFunc)_stream_has_name,
+                                    (char *)name);
+        return stream;
+}
+
+static void
+update_default_source_from_name (GvcMixerControl *control,
+                                 const char      *name)
+{
+        gboolean changed;
+
+        if ((control->priv->default_source_name == NULL
+             && name != NULL)
+            || (control->priv->default_source_name != NULL
+                && name == NULL)
+            || strcmp (control->priv->default_source_name, name) != 0) {
+                changed = TRUE;
+        }
+
+        if (changed) {
+                GvcMixerStream *stream;
+
+                g_free (control->priv->default_source_name);
+                control->priv->default_source_name = g_strdup (name);
+
+                stream = find_stream_for_name (control, name);
+                _set_default_source (control, stream);
+        }
+}
+
+static void
+update_default_sink_from_name (GvcMixerControl *control,
+                               const char      *name)
+{
+        gboolean changed;
+
+        if ((control->priv->default_sink_name == NULL
+             && name != NULL)
+            || (control->priv->default_sink_name != NULL
+                && name == NULL)
+            || strcmp (control->priv->default_sink_name, name) != 0) {
+                changed = TRUE;
+        }
+
+        if (changed) {
+                GvcMixerStream *stream;
+                g_free (control->priv->default_sink_name);
+                control->priv->default_sink_name = g_strdup (name);
+
+                stream = find_stream_for_name (control, name);
+                _set_default_sink (control, stream);
+        }
+}
+
+static void
+update_server (GvcMixerControl      *control,
+               const pa_server_info *info)
+{
+        if (info->default_source_name != NULL) {
+                update_default_source_from_name (control, info->default_source_name);
+        }
+        if (info->default_sink_name != NULL) {
+                update_default_sink_from_name (control, info->default_sink_name);
+        }
+}
+
+static void
+remove_stream (GvcMixerControl *control,
+               GvcMixerStream  *stream)
+{
+        guint id;
+
+        g_object_ref (stream);
+
+        id = gvc_mixer_stream_get_id (stream);
+
+        if (id == control->priv->default_sink_id) {
+                _set_default_sink (control, NULL);
+        } else if (id == control->priv->default_source_id) {
+                _set_default_source (control, NULL);
+        }
+
+        g_hash_table_remove (control->priv->all_streams,
+                             GUINT_TO_POINTER (id));
+        g_signal_emit (G_OBJECT (control),
+                       signals[STREAM_REMOVED],
+                       0,
+                       gvc_mixer_stream_get_id (stream));
+        g_object_unref (stream);
+}
+
+static void
+add_stream (GvcMixerControl *control,
+            GvcMixerStream  *stream)
+{
+        g_hash_table_insert (control->priv->all_streams,
+                             GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
+                             stream);
+        g_signal_emit (G_OBJECT (control),
+                       signals[STREAM_ADDED],
+                       0,
+                       gvc_mixer_stream_get_id (stream));
+}
+
+static void
+update_sink (GvcMixerControl    *control,
+             const pa_sink_info *info)
+{
+        GvcMixerStream *stream;
+        gboolean        is_new;
+        pa_volume_t     max_volume;
+        char            map_buff[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+        pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map);
+#if 1
+        g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'",
+                 info->index,
+                 info->name,
+                 info->description,
+                 map_buff);
+#endif
+
+        /* for now completely ignore virtual streams */
+        if (!(info->flags & PA_SINK_HARDWARE)) {
+                return;
+        }
+
+        is_new = FALSE;
+        stream = g_hash_table_lookup (control->priv->sinks,
+                                      GUINT_TO_POINTER (info->index));
+        if (stream == NULL) {
+                GvcChannelMap *map;
+                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
+                stream = gvc_mixer_sink_new (control->priv->pa_context,
+                                             info->index,
+                                             map);
+                g_object_unref (map);
+                is_new = TRUE;
+        }
+
+        max_volume = pa_cvolume_max (&info->volume);
+        gvc_mixer_stream_set_name (stream, info->name);
+        gvc_mixer_stream_set_description (stream, info->description);
+        gvc_mixer_stream_set_icon_name (stream, "audio-card");
+        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
+        gvc_mixer_stream_set_is_muted (stream, info->mute);
+        gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
+        if (!!(info->flags & PA_SINK_DECIBEL_VOLUME)) {
+                gdouble db;
+                db = pa_sw_volume_to_dB (max_volume);
+                gvc_mixer_stream_set_decibel (stream, db);
+        }
+
+        if (is_new) {
+                g_hash_table_insert (control->priv->sinks,
+                                     GUINT_TO_POINTER (info->index),
+                                     g_object_ref (stream));
+                add_stream (control, stream);
+        }
+
+        if (control->priv->default_sink_name != NULL
+            && info->name != NULL
+            && strcmp (control->priv->default_sink_name, info->name) == 0) {
+                _set_default_sink (control, stream);
+        }
+}
+
+static void
+update_source (GvcMixerControl      *control,
+               const pa_source_info *info)
+{
+        GvcMixerStream *stream;
+        gboolean        is_new;
+        pa_volume_t     max_volume;
+
+#if 1
+        g_debug ("Updating source: index=%u name='%s' description='%s'",
+                 info->index,
+                 info->name,
+                 info->description);
+#endif
+
+        /* for now completely ignore virtual streams */
+        if (!(info->flags & PA_SOURCE_HARDWARE)) {
+                return;
+        }
+
+        is_new = FALSE;
+
+        stream = g_hash_table_lookup (control->priv->sources,
+                                      GUINT_TO_POINTER (info->index));
+        if (stream == NULL) {
+                GvcChannelMap *map;
+                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
+                stream = gvc_mixer_source_new (control->priv->pa_context,
+                                               info->index,
+                                               map);
+                g_object_unref (map);
+                is_new = TRUE;
+        }
+
+        max_volume = pa_cvolume_max (&info->volume);
+
+        gvc_mixer_stream_set_name (stream, info->name);
+        gvc_mixer_stream_set_description (stream, info->description);
+        gvc_mixer_stream_set_icon_name (stream, "audio-input-microphone");
+        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
+        gvc_mixer_stream_set_is_muted (stream, info->mute);
+        gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
+        if (!!(info->flags & PA_SINK_DECIBEL_VOLUME)) {
+                gdouble db;
+                db = pa_sw_volume_to_dB (max_volume);
+                gvc_mixer_stream_set_decibel (stream, db);
+        }
+
+        if (is_new) {
+                g_hash_table_insert (control->priv->sources,
+                                     GUINT_TO_POINTER (info->index),
+                                     g_object_ref (stream));
+                add_stream (control, stream);
+        }
+
+        if (control->priv->default_source_name != NULL
+            && info->name != NULL
+            && strcmp (control->priv->default_source_name, info->name) == 0) {
+                _set_default_source (control, stream);
+        }
+}
+
+static void
+set_icon_name_from_proplist (GvcMixerStream *stream,
+                             pa_proplist    *l,
+                             const char     *default_icon_name)
+{
+        const char *t;
+
+        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) {
+                goto finish;
+        }
+
+        if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) {
+                goto finish;
+        }
+
+        if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) {
+                goto finish;
+        }
+
+        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
+
+                if (strcmp (t, "video") == 0 ||
+                    strcmp (t, "phone") == 0) {
+                        goto finish;
+                }
+
+                if (strcmp (t, "music") == 0) {
+                        t = "audio";
+                        goto finish;
+                }
+
+                if (strcmp (t, "game") == 0) {
+                        t = "applications-games";
+                        goto finish;
+                }
+
+                if (strcmp (t, "event") == 0) {
+                        t = "dialog-information";
+                        goto finish;
+                }
+        }
+
+        t = default_icon_name;
+
+ finish:
+        gvc_mixer_stream_set_icon_name (stream, t);
+}
+
+static void
+update_sink_input (GvcMixerControl          *control,
+                   const pa_sink_input_info *info)
+{
+        GvcMixerStream *stream;
+        gboolean        is_new;
+        pa_volume_t     max_volume;
+        const char     *name;
+
+#if 0
+        g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u",
+                 info->index,
+                 info->name,
+                 info->client,
+                 info->sink);
+#endif
+
+        is_new = FALSE;
+
+        stream = g_hash_table_lookup (control->priv->sink_inputs,
+                                      GUINT_TO_POINTER (info->index));
+        if (stream == NULL) {
+                GvcChannelMap *map;
+                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
+                stream = gvc_mixer_sink_input_new (control->priv->pa_context,
+                                                   info->index,
+                                                   map);
+                g_object_unref (map);
+                is_new = TRUE;
+        }
+
+        max_volume = pa_cvolume_max (&info->volume);
+
+        name = (const char *)g_hash_table_lookup (control->priv->clients,
+                                                  GUINT_TO_POINTER (info->client));
+        gvc_mixer_stream_set_name (stream, name);
+        gvc_mixer_stream_set_description (stream, info->name);
+
+        set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia");
+        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
+        gvc_mixer_stream_set_is_muted (stream, info->mute);
+
+        if (is_new) {
+                g_hash_table_insert (control->priv->sink_inputs,
+                                     GUINT_TO_POINTER (info->index),
+                                     g_object_ref (stream));
+                add_stream (control, stream);
+        }
+}
+
+static void
+update_source_output (GvcMixerControl             *control,
+                      const pa_source_output_info *info)
+{
+        GvcMixerStream *stream;
+        gboolean        is_new;
+        const char     *name;
+
+#if 1
+        g_debug ("Updating source output: index=%u name='%s' client=%u source=%u",
+                 info->index,
+                 info->name,
+                 info->client,
+                 info->source);
+#endif
+
+        is_new = FALSE;
+        stream = g_hash_table_lookup (control->priv->source_outputs,
+                                      GUINT_TO_POINTER (info->index));
+        if (stream == NULL) {
+                GvcChannelMap *map;
+                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
+                stream = gvc_mixer_source_output_new (control->priv->pa_context,
+                                                      info->index,
+                                                      map);
+                g_object_unref (map);
+                is_new = TRUE;
+        }
+
+        name = (const char *)g_hash_table_lookup (control->priv->clients,
+                                                  GUINT_TO_POINTER (info->client));
+
+        gvc_mixer_stream_set_name (stream, name);
+        gvc_mixer_stream_set_description (stream, info->name);
+        set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia");
+
+        if (is_new) {
+                g_hash_table_insert (control->priv->source_outputs,
+                                     GUINT_TO_POINTER (info->index),
+                                     g_object_ref (stream));
+                add_stream (control, stream);
+        }
+}
+
+static void
+update_client (GvcMixerControl      *control,
+               const pa_client_info *info)
+{
+#if 1
+        g_debug ("Updating client: index=%u name='%s'",
+                 info->index,
+                 info->name);
+#endif
+        g_hash_table_insert (control->priv->clients,
+                             GUINT_TO_POINTER (info->index),
+                             g_strdup (info->name));
+}
+
+static void
+_pa_context_get_sink_info_cb (pa_context         *context,
+                              const pa_sink_info *i,
+                              int                 eol,
+                              void               *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
+                        return;
+                }
+
+                g_warning ("Sink callback failure");
+                return;
+        }
+
+        if (eol > 0) {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_sink (control, i);
+}
+
+
+static void
+_pa_context_get_source_info_cb (pa_context           *context,
+                                const pa_source_info *i,
+                                int                   eol,
+                                void                 *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
+                        return;
+                }
+
+                g_warning ("Source callback failure");
+                return;
+        }
+
+        if (eol > 0) {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_source (control, i);
+}
+
+static void
+_pa_context_get_sink_input_info_cb (pa_context               *context,
+                                    const pa_sink_input_info *i,
+                                    int                       eol,
+                                    void                     *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
+                        return;
+                }
+
+                g_warning ("Sink input callback failure");
+                return;
+        }
+
+        if (eol > 0) {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_sink_input (control, i);
+}
+
+static void
+_pa_context_get_source_output_info_cb (pa_context                  *context,
+                                       const pa_source_output_info *i,
+                                       int                          eol,
+                                       void                        *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
+                        return;
+                }
+
+                g_warning ("Source output callback failure");
+                return;
+        }
+
+        if (eol > 0)  {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_source_output (control, i);
+}
+
+static void
+_pa_context_get_client_info_cb (pa_context           *context,
+                                const pa_client_info *i,
+                                int                   eol,
+                                void                 *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
+                        return;
+                }
+
+                g_warning ("Client callback failure");
+                return;
+        }
+
+        if (eol > 0) {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_client (control, i);
+}
+
+static void
+_pa_context_get_server_info_cb (pa_context           *context,
+                                const pa_server_info *i,
+                                void                 *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (i == NULL) {
+                g_warning ("Server info callback failure");
+                return;
+        }
+
+        update_server (control, i);
+        dec_outstanding (control);
+}
+
+static void
+remove_event_role_stream (GvcMixerControl *control)
+{
+        g_debug ("Removing event role");
+}
+
+static void
+update_event_role_stream (GvcMixerControl                  *control,
+                          const pa_ext_stream_restore_info *info)
+{
+        GvcMixerStream *stream;
+        gboolean        is_new;
+        pa_volume_t     max_volume;
+
+        if (strcmp (info->name, "sink-input-by-media-role:event") != 0) {
+                return;
+        }
+
+#if 0
+        g_debug ("Updating event role: name='%s' device='%s'",
+                 info->name,
+                 info->device);
+#endif
+
+        is_new = FALSE;
+
+        if (!control->priv->event_sink_input_is_set) {
+                stream = gvc_mixer_event_role_new (control->priv->pa_context,
+                                                   info->device);
+                control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream);
+                control->priv->event_sink_input_is_set = TRUE;
+
+                is_new = TRUE;
+        } else {
+                stream = g_hash_table_lookup (control->priv->all_streams,
+                                              GUINT_TO_POINTER (control->priv->event_sink_input_id));
+        }
+
+        max_volume = pa_cvolume_max (&info->volume);
+
+        gvc_mixer_stream_set_name (stream, _("System Sounds"));
+        gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control");
+        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
+        gvc_mixer_stream_set_is_muted (stream, info->mute);
+
+        if (is_new) {
+                add_stream (control, stream);
+        }
+}
+
+static void
+_pa_ext_stream_restore_read_cb (pa_context                       *context,
+                                const pa_ext_stream_restore_info *i,
+                                int                               eol,
+                                void                             *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        if (eol < 0) {
+                g_debug ("Failed to initialized stream_restore extension: %s",
+                         pa_strerror (pa_context_errno (context)));
+                remove_event_role_stream (control);
+                return;
+        }
+
+        if (eol > 0) {
+                dec_outstanding (control);
+                return;
+        }
+
+        update_event_role_stream (control, i);
+}
+
+static void
+_pa_ext_stream_restore_subscribe_cb (pa_context *context,
+                                     void       *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+        pa_operation    *o;
+
+        o = pa_ext_stream_restore_read (context,
+                                        _pa_ext_stream_restore_read_cb,
+                                        control);
+        if (o == NULL) {
+                g_warning ("pa_ext_stream_restore_read() failed");
+                return;
+        }
+
+        pa_operation_unref (o);
+}
+
+static void
+req_update_server_info (GvcMixerControl *control,
+                        int              index)
+{
+        pa_operation *o;
+
+        o = pa_context_get_server_info (control->priv->pa_context,
+                                        _pa_context_get_server_info_cb,
+                                        control);
+        if (o == NULL) {
+                g_warning ("pa_context_get_server_info() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
+req_update_client_info (GvcMixerControl *control,
+                        int              index)
+{
+        pa_operation *o;
+
+        if (index < 0) {
+                o = pa_context_get_client_info_list (control->priv->pa_context,
+                                                     _pa_context_get_client_info_cb,
+                                                     control);
+        } else {
+                o = pa_context_get_client_info (control->priv->pa_context,
+                                                index,
+                                                _pa_context_get_client_info_cb,
+                                                control);
+        }
+
+        if (o == NULL) {
+                g_warning ("pa_context_client_info_list() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
+req_update_sink_info (GvcMixerControl *control,
+                      int              index)
+{
+        pa_operation *o;
+
+        if (index < 0) {
+                o = pa_context_get_sink_info_list (control->priv->pa_context,
+                                                   _pa_context_get_sink_info_cb,
+                                                   control);
+        } else {
+                o = pa_context_get_sink_info_by_index (control->priv->pa_context,
+                                                       index,
+                                                       _pa_context_get_sink_info_cb,
+                                                       control);
+        }
+
+        if (o == NULL) {
+                g_warning ("pa_context_get_sink_info_list() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
+req_update_source_info (GvcMixerControl *control,
+                        int              index)
+{
+        pa_operation *o;
+
+        if (index < 0) {
+                o = pa_context_get_source_info_list (control->priv->pa_context,
+                                                     _pa_context_get_source_info_cb,
+                                                     control);
+        } else {
+                o = pa_context_get_source_info_by_index(control->priv->pa_context,
+                                                        index,
+                                                        _pa_context_get_source_info_cb,
+                                                        control);
+        }
+
+        if (o == NULL) {
+                g_warning ("pa_context_get_source_info_list() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
+req_update_sink_input_info (GvcMixerControl *control,
+                            int              index)
+{
+        pa_operation *o;
+
+        if (index < 0) {
+                o = pa_context_get_sink_input_info_list (control->priv->pa_context,
+                                                         _pa_context_get_sink_input_info_cb,
+                                                         control);
+        } else {
+                o = pa_context_get_sink_input_info (control->priv->pa_context,
+                                                    index,
+                                                    _pa_context_get_sink_input_info_cb,
+                                                    control);
+        }
+
+        if (o == NULL) {
+                g_warning ("pa_context_get_sink_input_info_list() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
+req_update_source_output_info (GvcMixerControl *control,
+                               int              index)
+{
+        pa_operation *o;
+
+        if (index < 0) {
+                o = pa_context_get_source_output_info_list (control->priv->pa_context,
+                                                            _pa_context_get_source_output_info_cb,
+                                                            control);
+        } else {
+                o = pa_context_get_source_output_info (control->priv->pa_context,
+                                                       index,
+                                                       _pa_context_get_source_output_info_cb,
+                                                       control);
+        }
+
+        if (o == NULL) {
+                g_warning ("pa_context_get_source_output_info_list() failed");
+                return;
+        }
+        pa_operation_unref (o);
+}
+
+static void
+remove_client (GvcMixerControl *control,
+               guint            index)
+{
+        g_hash_table_remove (control->priv->clients,
+                             GUINT_TO_POINTER (index));
+}
+
+static void
+remove_sink (GvcMixerControl *control,
+             guint            index)
+{
+        GvcMixerStream *stream;
+
+#if 0
+        g_debug ("Removing sink: index=%u", index);
+#endif
+
+        stream = g_hash_table_lookup (control->priv->sinks,
+                                      GUINT_TO_POINTER (index));
+        if (stream == NULL) {
+                return;
+        }
+        g_hash_table_remove (control->priv->sinks,
+                             GUINT_TO_POINTER (index));
+
+        remove_stream (control, stream);
+}
+
+static void
+remove_source (GvcMixerControl *control,
+               guint            index)
+{
+        GvcMixerStream *stream;
+
+#if 0
+        g_debug ("Removing source: index=%u", index);
+#endif
+
+        stream = g_hash_table_lookup (control->priv->sources,
+                                      GUINT_TO_POINTER (index));
+        if (stream == NULL) {
+                return;
+        }
+        g_hash_table_remove (control->priv->sources,
+                             GUINT_TO_POINTER (index));
+
+        remove_stream (control, stream);
+}
+
+static void
+remove_sink_input (GvcMixerControl *control,
+                   guint            index)
+{
+        GvcMixerStream *stream;
+
+#if 0
+        g_debug ("Removing sink input: index=%u", index);
+#endif
+        stream = g_hash_table_lookup (control->priv->sink_inputs,
+                                      GUINT_TO_POINTER (index));
+        if (stream == NULL) {
+                return;
+        }
+        g_hash_table_remove (control->priv->sink_inputs,
+                             GUINT_TO_POINTER (index));
+
+        remove_stream (control, stream);
+}
+
+static void
+remove_source_output (GvcMixerControl *control,
+                      guint            index)
+{
+        GvcMixerStream *stream;
+
+#if 0
+        g_debug ("Removing source output: index=%u", index);
+#endif
+
+        stream = g_hash_table_lookup (control->priv->source_outputs,
+                                      GUINT_TO_POINTER (index));
+        if (stream == NULL) {
+                return;
+        }
+        g_hash_table_remove (control->priv->source_outputs,
+                             GUINT_TO_POINTER (index));
+
+        remove_stream (control, stream);
+}
+
+static void
+_pa_context_subscribe_cb (pa_context                  *context,
+                          pa_subscription_event_type_t t,
+                          uint32_t                     index,
+                          void                        *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+        case PA_SUBSCRIPTION_EVENT_SINK:
+                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                        remove_sink (control, index);
+                } else {
+                        req_update_sink_info (control, index);
+                }
+                break;
+
+        case PA_SUBSCRIPTION_EVENT_SOURCE:
+                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                        remove_source (control, index);
+                } else {
+                        req_update_source_info (control, index);
+                }
+                break;
+
+        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                        remove_sink_input (control, index);
+                } else {
+                        req_update_sink_input_info (control, index);
+                }
+                break;
+
+        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
+                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                        remove_source_output (control, index);
+                } else {
+                        req_update_source_output_info (control, index);
+                }
+                break;
+
+        case PA_SUBSCRIPTION_EVENT_CLIENT:
+                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                        remove_client (control, index);
+                } else {
+                        req_update_client_info (control, index);
+                }
+                break;
+
+        case PA_SUBSCRIPTION_EVENT_SERVER:
+                req_update_server_info (control, index);
+                break;
+        }
+}
+
+static void
+gvc_mixer_control_ready (GvcMixerControl *control)
+{
+        pa_operation *o;
+
+        pa_context_set_subscribe_callback (control->priv->pa_context,
+                                           _pa_context_subscribe_cb,
+                                           control);
+        o = pa_context_subscribe (control->priv->pa_context,
+                                  (pa_subscription_mask_t)
+                                  (PA_SUBSCRIPTION_MASK_SINK|
+                                   PA_SUBSCRIPTION_MASK_SOURCE|
+                                   PA_SUBSCRIPTION_MASK_SINK_INPUT|
+                                   PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
+                                   PA_SUBSCRIPTION_MASK_CLIENT|
+                                   PA_SUBSCRIPTION_MASK_SERVER),
+                                  NULL,
+                                  NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_subscribe() failed");
+                return;
+        }
+        pa_operation_unref (o);
+
+        req_update_server_info (control, -1);
+        req_update_client_info (control, -1);
+        req_update_sink_info (control, -1);
+        req_update_source_info (control, -1);
+        req_update_sink_input_info (control, -1);
+        req_update_source_output_info (control, -1);
+
+        control->priv->n_outstanding = 6;
+
+        /* This call is not always supported */
+        o = pa_ext_stream_restore_read (control->priv->pa_context,
+                                        _pa_ext_stream_restore_read_cb,
+                                        control);
+        if (o != NULL) {
+                pa_operation_unref (o);
+                control->priv->n_outstanding++;
+
+                pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,
+                                                        _pa_ext_stream_restore_subscribe_cb,
+                                                        control);
+
+
+                o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
+                                                     1,
+                                                     NULL,
+                                                     NULL);
+                if (o != NULL) {
+                        pa_operation_unref (o);
+                }
+
+        } else {
+                g_debug ("Failed to initialized stream_restore extension: %s",
+                         pa_strerror (pa_context_errno (control->priv->pa_context)));
+        }
+}
+
+static void
+_pa_context_state_cb (pa_context *context,
+                      void       *userdata)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
+
+        switch (pa_context_get_state (context)) {
+        case PA_CONTEXT_UNCONNECTED:
+        case PA_CONTEXT_CONNECTING:
+        case PA_CONTEXT_AUTHORIZING:
+        case PA_CONTEXT_SETTING_NAME:
+                break;
+
+        case PA_CONTEXT_READY:
+                gvc_mixer_control_ready (control);
+                break;
+
+        case PA_CONTEXT_FAILED:
+                g_warning ("Connection failed");
+                break;
+
+        case PA_CONTEXT_TERMINATED:
+        default:
+                /* FIXME: */
+                break;
+        }
+}
+
+gboolean
+gvc_mixer_control_open (GvcMixerControl *control)
+{
+        int res;
+
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
+        g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
+        g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);
+
+        pa_context_set_state_callback (control->priv->pa_context,
+                                       _pa_context_state_cb,
+                                       control);
+
+        res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) 0, NULL);
+        if (res < 0) {
+                g_warning ("Failed to connect context: %s",
+                           pa_strerror (pa_context_errno (control->priv->pa_context)));
+        }
+
+        return res;
+}
+
+gboolean
+gvc_mixer_control_close (GvcMixerControl *control)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
+        g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
+
+        pa_context_disconnect (control->priv->pa_context);
+        return TRUE;
+}
+
+static void
+gvc_mixer_control_dispose (GObject *object)
+{
+        GvcMixerControl *control = GVC_MIXER_CONTROL (object);
+
+        if (control->priv->pa_context != NULL) {
+                pa_context_unref (control->priv->pa_context);
+                control->priv->pa_context = NULL;
+        }
+
+        if (control->priv->default_source_name != NULL) {
+                g_free (control->priv->default_source_name);
+                control->priv->default_source_name = NULL;
+        }
+        if (control->priv->default_sink_name != NULL) {
+                g_free (control->priv->default_sink_name);
+                control->priv->default_sink_name = NULL;
+        }
+
+        if (control->priv->pa_mainloop != NULL) {
+                pa_glib_mainloop_free (control->priv->pa_mainloop);
+                control->priv->pa_mainloop = NULL;
+        }
+
+        if (control->priv->all_streams != NULL) {
+                g_hash_table_destroy (control->priv->all_streams);
+                control->priv->all_streams = NULL;
+        }
+
+        if (control->priv->sinks != NULL) {
+                g_hash_table_destroy (control->priv->sinks);
+                control->priv->sinks = NULL;
+        }
+        if (control->priv->sources != NULL) {
+                g_hash_table_destroy (control->priv->sources);
+                control->priv->sources = NULL;
+        }
+        if (control->priv->sink_inputs != NULL) {
+                g_hash_table_destroy (control->priv->sink_inputs);
+                control->priv->sink_inputs = NULL;
+        }
+        if (control->priv->source_outputs != NULL) {
+                g_hash_table_destroy (control->priv->source_outputs);
+                control->priv->source_outputs = NULL;
+        }
+        if (control->priv->clients != NULL) {
+                g_hash_table_destroy (control->priv->clients);
+                control->priv->clients = NULL;
+        }
+
+        G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
+}
+
+static GObject *
+gvc_mixer_control_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_params)
+{
+        GObject         *object;
+        GvcMixerControl *self;
+        pa_proplist     *proplist;
+
+        object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_CONTROL (object);
+
+        /* FIXME: read these from an object property */
+        proplist = pa_proplist_new ();
+        pa_proplist_sets (proplist,
+                          PA_PROP_APPLICATION_NAME,
+                          _("GNOME Volume Control"));
+        pa_proplist_sets (proplist,
+                          PA_PROP_APPLICATION_ID,
+                          "org.gnome.VolumeControl");
+        pa_proplist_sets (proplist,
+                          PA_PROP_APPLICATION_ICON_NAME,
+                          "multimedia-volume-control");
+        pa_proplist_sets (proplist,
+                          PA_PROP_APPLICATION_VERSION,
+                          PACKAGE_VERSION);
+
+        self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);
+        g_assert (self->priv->pa_context);
+        pa_proplist_free (proplist);
+
+        return object;
+}
+
+static void
+gvc_mixer_control_class_init (GvcMixerControlClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->constructor = gvc_mixer_control_constructor;
+        object_class->dispose = gvc_mixer_control_dispose;
+        object_class->finalize = gvc_mixer_control_finalize;
+
+        signals [READY] =
+                g_signal_new ("ready",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, ready),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [STREAM_ADDED] =
+                g_signal_new ("stream-added",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_added),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__UINT,
+                              G_TYPE_NONE, 1, G_TYPE_UINT);
+        signals [STREAM_REMOVED] =
+                g_signal_new ("stream-removed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__UINT,
+                              G_TYPE_NONE, 1, G_TYPE_UINT);
+        signals [DEFAULT_SINK_CHANGED] =
+                g_signal_new ("default-sink-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__UINT,
+                              G_TYPE_NONE, 1, G_TYPE_UINT);
+        signals [DEFAULT_SOURCE_CHANGED] =
+                g_signal_new ("default-source-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__UINT,
+                              G_TYPE_NONE, 1, G_TYPE_UINT);
+
+        g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate));
+}
+
+static void
+gvc_mixer_control_init (GvcMixerControl *control)
+{
+        control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control);
+
+        control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());
+        g_assert (control->priv->pa_mainloop);
+
+        control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);
+        g_assert (control->priv->pa_api);
+
+        control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+        control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+        control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+        control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+        control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+
+        control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
+}
+
+static void
+gvc_mixer_control_finalize (GObject *object)
+{
+        GvcMixerControl *mixer_control;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_CONTROL (object));
+
+        mixer_control = GVC_MIXER_CONTROL (object);
+
+        g_return_if_fail (mixer_control->priv != NULL);
+        G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
+}
+
+GvcMixerControl *
+gvc_mixer_control_new (void)
+{
+        GObject *control;
+        control = g_object_new (GVC_TYPE_MIXER_CONTROL, NULL);
+        return GVC_MIXER_CONTROL (control);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-control.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-control.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,89 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_CONTROL_H
+#define __GVC_MIXER_CONTROL_H
+
+#include <glib-object.h>
+#include <pulse/pulseaudio.h>
+#include "gvc-mixer-stream.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_CONTROL         (gvc_mixer_control_get_type ())
+#define GVC_MIXER_CONTROL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControl))
+#define GVC_MIXER_CONTROL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass))
+#define GVC_IS_MIXER_CONTROL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CONTROL))
+#define GVC_IS_MIXER_CONTROL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CONTROL))
+#define GVC_MIXER_CONTROL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass))
+
+typedef struct GvcMixerControlPrivate GvcMixerControlPrivate;
+
+typedef struct
+{
+        GObject                 parent;
+        GvcMixerControlPrivate *priv;
+} GvcMixerControl;
+
+typedef struct
+{
+        GObjectClass            parent_class;
+
+        void (*ready)                  (GvcMixerControl *control);
+        void (*stream_added)           (GvcMixerControl *control,
+                                        guint            id);
+        void (*stream_removed)         (GvcMixerControl *control,
+                                        guint            id);
+        void (*default_sink_changed)   (GvcMixerControl *control,
+                                        guint            id);
+        void (*default_source_changed) (GvcMixerControl *control,
+                                        guint            id);
+} GvcMixerControlClass;
+
+GType               gvc_mixer_control_get_type            (void);
+
+GvcMixerControl *   gvc_mixer_control_new                 (void);
+
+gboolean            gvc_mixer_control_open                (GvcMixerControl *control);
+gboolean            gvc_mixer_control_close               (GvcMixerControl *control);
+gboolean            gvc_mixer_control_is_ready            (GvcMixerControl *control);
+
+pa_context *        gvc_mixer_control_get_pa_context      (GvcMixerControl *control);
+GSList *            gvc_mixer_control_get_streams         (GvcMixerControl *control);
+GSList *            gvc_mixer_control_get_sinks           (GvcMixerControl *control);
+GSList *            gvc_mixer_control_get_sources         (GvcMixerControl *control);
+GSList *            gvc_mixer_control_get_sink_inputs     (GvcMixerControl *control);
+GSList *            gvc_mixer_control_get_source_outputs  (GvcMixerControl *control);
+
+GvcMixerStream *    gvc_mixer_control_lookup_stream_id    (GvcMixerControl *control,
+                                                           guint            id);
+
+GvcMixerStream *    gvc_mixer_control_get_default_sink     (GvcMixerControl *control);
+GvcMixerStream *    gvc_mixer_control_get_default_source   (GvcMixerControl *control);
+GvcMixerStream *    gvc_mixer_control_get_event_sink_input (GvcMixerControl *control);
+
+gboolean            gvc_mixer_control_set_default_sink     (GvcMixerControl *control,
+                                                            GvcMixerStream  *stream);
+gboolean            gvc_mixer_control_set_default_source   (GvcMixerControl *control,
+                                                            GvcMixerStream  *stream);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_CONTROL_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-event-role.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-event-role.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,238 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/ext-stream-restore.h>
+
+#include "gvc-mixer-event-role.h"
+
+#define GVC_MIXER_EVENT_ROLE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRolePrivate))
+
+struct GvcMixerEventRolePrivate
+{
+        char          *device;
+};
+
+enum
+{
+        PROP_0,
+        PROP_DEVICE
+};
+
+static void     gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass);
+static void     gvc_mixer_event_role_init       (GvcMixerEventRole      *mixer_event_role);
+static void     gvc_mixer_event_role_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcMixerEventRole, gvc_mixer_event_role, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+update_settings (GvcMixerEventRole *role,
+                 guint              volume,
+                 gboolean           is_muted)
+{
+        pa_operation              *o;
+        guint                      index;
+        pa_context                *context;
+        pa_ext_stream_restore_info info;
+
+        index = gvc_mixer_stream_get_index (GVC_MIXER_STREAM (role));
+
+        pa_cvolume_set (&info.volume, 1, (pa_volume_t)volume);
+
+        info.name = "sink-input-by-media-role:event";
+        info.channel_map.channels = 1;
+        info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO;
+        info.device = role->priv->device;
+        info.mute = is_muted;
+
+        context = gvc_mixer_stream_get_pa_context (GVC_MIXER_STREAM (role));
+
+        o = pa_ext_stream_restore_write (context,
+                                         PA_UPDATE_REPLACE,
+                                         &info,
+                                         1,
+                                         TRUE,
+                                         NULL,
+                                         NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_ext_stream_restore_write() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static gboolean
+gvc_mixer_event_role_change_volume (GvcMixerStream *stream,
+                                    guint           volume)
+{
+        return update_settings (GVC_MIXER_EVENT_ROLE (stream),
+                                volume,
+                                gvc_mixer_stream_get_is_muted (stream));
+}
+
+static gboolean
+gvc_mixer_event_role_change_is_muted (GvcMixerStream *stream,
+                                      gboolean        is_muted)
+{
+        return update_settings (GVC_MIXER_EVENT_ROLE (stream),
+                                gvc_mixer_stream_get_volume (stream),
+                                is_muted);
+}
+
+static gboolean
+gvc_mixer_event_role_set_device (GvcMixerEventRole *role,
+                                 const char        *device)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_EVENT_ROLE (role), FALSE);
+
+        g_free (role->priv->device);
+        role->priv->device = g_strdup (device);
+        g_object_notify (G_OBJECT (role), "device");
+
+        return TRUE;
+}
+
+static void
+gvc_mixer_event_role_set_property (GObject       *object,
+                                   guint          prop_id,
+                                   const GValue  *value,
+                                   GParamSpec    *pspec)
+{
+        GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object);
+
+        switch (prop_id) {
+        case PROP_DEVICE:
+                gvc_mixer_event_role_set_device (self, g_value_get_string (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gvc_mixer_event_role_get_property (GObject     *object,
+                                   guint        prop_id,
+                                   GValue      *value,
+                                   GParamSpec  *pspec)
+{
+        GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object);
+
+        switch (prop_id) {
+        case PROP_DEVICE:
+                g_value_set_string (value, self->priv->device);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+gvc_mixer_event_role_constructor (GType                  type,
+                                  guint                  n_construct_properties,
+                                  GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerEventRole *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_EVENT_ROLE (object);
+
+        return object;
+}
+
+static void
+gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass)
+{
+        GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+        GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass);
+
+        object_class->constructor = gvc_mixer_event_role_constructor;
+        object_class->finalize = gvc_mixer_event_role_finalize;
+        object_class->set_property = gvc_mixer_event_role_set_property;
+        object_class->get_property = gvc_mixer_event_role_get_property;
+
+        stream_class->change_volume = gvc_mixer_event_role_change_volume;
+        stream_class->change_is_muted = gvc_mixer_event_role_change_is_muted;
+
+        g_object_class_install_property (object_class,
+                                         PROP_DEVICE,
+                                         g_param_spec_string ("device",
+                                                              "Device",
+                                                              "Device",
+                                                              NULL,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+
+        g_type_class_add_private (klass, sizeof (GvcMixerEventRolePrivate));
+}
+
+static void
+gvc_mixer_event_role_init (GvcMixerEventRole *event_role)
+{
+        event_role->priv = GVC_MIXER_EVENT_ROLE_GET_PRIVATE (event_role);
+
+}
+
+static void
+gvc_mixer_event_role_finalize (GObject *object)
+{
+        GvcMixerEventRole *mixer_event_role;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_EVENT_ROLE (object));
+
+        mixer_event_role = GVC_MIXER_EVENT_ROLE (object);
+
+        g_return_if_fail (mixer_event_role->priv != NULL);
+
+        g_free (mixer_event_role->priv->device);
+
+        G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->finalize (object);
+}
+
+GvcMixerStream *
+gvc_mixer_event_role_new (pa_context *context,
+                          const char *device)
+{
+        GObject *object;
+
+        object = g_object_new (GVC_TYPE_MIXER_EVENT_ROLE,
+                               "pa-context", context,
+                               "index", 0,
+                               "device", device,
+                               NULL);
+
+        return GVC_MIXER_STREAM (object);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-event-role.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-event-role.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_EVENT_ROLE_H
+#define __GVC_MIXER_EVENT_ROLE_H
+
+#include <glib-object.h>
+#include "gvc-mixer-stream.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_EVENT_ROLE         (gvc_mixer_event_role_get_type ())
+#define GVC_MIXER_EVENT_ROLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRole))
+#define GVC_MIXER_EVENT_ROLE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass))
+#define GVC_IS_MIXER_EVENT_ROLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_EVENT_ROLE))
+#define GVC_IS_MIXER_EVENT_ROLE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_EVENT_ROLE))
+#define GVC_MIXER_EVENT_ROLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass))
+
+typedef struct GvcMixerEventRolePrivate GvcMixerEventRolePrivate;
+
+typedef struct
+{
+        GvcMixerStream            parent;
+        GvcMixerEventRolePrivate *priv;
+} GvcMixerEventRole;
+
+typedef struct
+{
+        GvcMixerStreamClass parent_class;
+} GvcMixerEventRoleClass;
+
+GType               gvc_mixer_event_role_get_type      (void);
+
+GvcMixerStream *    gvc_mixer_event_role_new           (pa_context *context,
+                                                        const char *device);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_EVENT_ROLE_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink-input.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink-input.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,186 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-sink-input.h"
+
+#define GVC_MIXER_SINK_INPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputPrivate))
+
+struct GvcMixerSinkInputPrivate
+{
+        gpointer dummy;
+};
+
+static void     gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass);
+static void     gvc_mixer_sink_input_init       (GvcMixerSinkInput      *mixer_sink_input);
+static void     gvc_mixer_sink_input_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcMixerSinkInput, gvc_mixer_sink_input, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_sink_input_change_volume (GvcMixerStream *stream,
+                                    guint           volume)
+{
+        pa_operation      *o;
+        guint              index;
+        GvcChannelMap     *map;
+        pa_context        *context;
+        pa_cvolume         cv;
+        guint              i;
+        guint              num_channels;
+        gdouble           *gains;
+
+        index = gvc_mixer_stream_get_index (stream);
+
+        map = gvc_mixer_stream_get_channel_map (stream);
+        num_channels = gvc_channel_map_get_num_channels (map);
+        gains = gvc_channel_map_get_gains (map);
+
+        /* set all values to nominal level */
+        pa_cvolume_set (&cv, num_channels, (pa_volume_t)volume);
+
+        /* apply channel gain mapping */
+        for (i = 0; i < num_channels; i++) {
+                pa_volume_t v;
+                v = (double) volume * gains[i];
+                cv.values[i] = v;
+        }
+
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_sink_input_volume (context,
+                                              index,
+                                              &cv,
+                                              NULL,
+                                              NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_sink_input_volume() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static gboolean
+gvc_mixer_sink_input_change_is_muted (GvcMixerStream *stream,
+                                      gboolean        is_muted)
+{
+        pa_operation *o;
+        guint         index;
+        pa_context   *context;
+
+        index = gvc_mixer_stream_get_index (stream);
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_sink_input_mute (context,
+                                            index,
+                                            is_muted,
+                                            NULL,
+                                            NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_sink_input_mute_by_index() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static GObject *
+gvc_mixer_sink_input_constructor (GType                  type,
+                                  guint                  n_construct_properties,
+                                  GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerSinkInput *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_SINK_INPUT (object);
+
+        return object;
+}
+
+static void
+gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass)
+{
+        GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+        GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass);
+
+        object_class->constructor = gvc_mixer_sink_input_constructor;
+        object_class->finalize = gvc_mixer_sink_input_finalize;
+
+        stream_class->change_volume = gvc_mixer_sink_input_change_volume;
+        stream_class->change_is_muted = gvc_mixer_sink_input_change_is_muted;
+
+        g_type_class_add_private (klass, sizeof (GvcMixerSinkInputPrivate));
+}
+
+static void
+gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input)
+{
+        sink_input->priv = GVC_MIXER_SINK_INPUT_GET_PRIVATE (sink_input);
+
+}
+
+static void
+gvc_mixer_sink_input_finalize (GObject *object)
+{
+        GvcMixerSinkInput *mixer_sink_input;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object));
+
+        mixer_sink_input = GVC_MIXER_SINK_INPUT (object);
+
+        g_return_if_fail (mixer_sink_input->priv != NULL);
+        G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->finalize (object);
+}
+
+GvcMixerStream *
+gvc_mixer_sink_input_new (pa_context    *context,
+                          guint          index,
+                          GvcChannelMap *channel_map)
+{
+        GObject *object;
+
+        object = g_object_new (GVC_TYPE_MIXER_SINK_INPUT,
+                               "pa-context", context,
+                               "index", index,
+                               "channel-map", channel_map,
+                               NULL);
+
+        return GVC_MIXER_STREAM (object);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink-input.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink-input.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_SINK_INPUT_H
+#define __GVC_MIXER_SINK_INPUT_H
+
+#include <glib-object.h>
+#include "gvc-mixer-stream.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_SINK_INPUT         (gvc_mixer_sink_input_get_type ())
+#define GVC_MIXER_SINK_INPUT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInput))
+#define GVC_MIXER_SINK_INPUT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass))
+#define GVC_IS_MIXER_SINK_INPUT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK_INPUT))
+#define GVC_IS_MIXER_SINK_INPUT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK_INPUT))
+#define GVC_MIXER_SINK_INPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass))
+
+typedef struct GvcMixerSinkInputPrivate GvcMixerSinkInputPrivate;
+
+typedef struct
+{
+        GvcMixerStream            parent;
+        GvcMixerSinkInputPrivate *priv;
+} GvcMixerSinkInput;
+
+typedef struct
+{
+        GvcMixerStreamClass parent_class;
+} GvcMixerSinkInputClass;
+
+GType               gvc_mixer_sink_input_get_type      (void);
+
+GvcMixerStream *    gvc_mixer_sink_input_new           (pa_context    *context,
+                                                        guint          index,
+                                                        GvcChannelMap *map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SINK_INPUT_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,191 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-sink.h"
+
+#define GVC_MIXER_SINK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkPrivate))
+
+struct GvcMixerSinkPrivate
+{
+        gpointer dummy;
+};
+
+static void     gvc_mixer_sink_class_init (GvcMixerSinkClass *klass);
+static void     gvc_mixer_sink_init       (GvcMixerSink      *mixer_sink);
+static void     gvc_mixer_sink_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcMixerSink, gvc_mixer_sink, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_sink_change_volume (GvcMixerStream *stream,
+                              guint           volume)
+{
+        pa_operation      *o;
+        guint              index;
+        GvcChannelMap     *map;
+        pa_context        *context;
+        pa_cvolume         cv;
+        guint              i;
+        guint              num_channels;
+        gdouble           *gains;
+
+        index = gvc_mixer_stream_get_index (stream);
+
+
+        map = gvc_mixer_stream_get_channel_map (stream);
+        num_channels = gvc_channel_map_get_num_channels (map);
+        gains = gvc_channel_map_get_gains (map);
+
+        g_debug ("Changing volume for sink: n=%d vol=%u", num_channels, (guint)volume);
+
+        /* set all values to nominal level */
+        pa_cvolume_set (&cv, num_channels, (pa_volume_t)volume);
+
+        /* apply channel gain mapping */
+        for (i = 0; i < num_channels; i++) {
+                pa_volume_t v;
+                v = (double) volume * gains[i];
+                g_debug ("Channel %d v=%u", i, v);
+                cv.values[i] = v;
+        }
+
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_sink_volume_by_index (context,
+                                                 index,
+                                                 &cv,
+                                                 NULL,
+                                                 NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_sink_volume_by_index() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static gboolean
+gvc_mixer_sink_change_is_muted (GvcMixerStream *stream,
+                                gboolean        is_muted)
+{
+        pa_operation *o;
+        guint         index;
+        pa_context   *context;
+
+        index = gvc_mixer_stream_get_index (stream);
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_sink_mute_by_index (context,
+                                               index,
+                                               is_muted,
+                                               NULL,
+                                               NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_sink_mute_by_index() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static GObject *
+gvc_mixer_sink_constructor (GType                  type,
+                            guint                  n_construct_properties,
+                            GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerSink *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_SINK (object);
+
+        return object;
+}
+
+static void
+gvc_mixer_sink_class_init (GvcMixerSinkClass *klass)
+{
+        GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+        GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass);
+
+        object_class->constructor = gvc_mixer_sink_constructor;
+        object_class->finalize = gvc_mixer_sink_finalize;
+
+        stream_class->change_volume = gvc_mixer_sink_change_volume;
+        stream_class->change_is_muted = gvc_mixer_sink_change_is_muted;
+
+        g_type_class_add_private (klass, sizeof (GvcMixerSinkPrivate));
+}
+
+static void
+gvc_mixer_sink_init (GvcMixerSink *sink)
+{
+        sink->priv = GVC_MIXER_SINK_GET_PRIVATE (sink);
+
+}
+
+static void
+gvc_mixer_sink_finalize (GObject *object)
+{
+        GvcMixerSink *mixer_sink;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_SINK (object));
+
+        mixer_sink = GVC_MIXER_SINK (object);
+
+        g_return_if_fail (mixer_sink->priv != NULL);
+        G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->finalize (object);
+}
+
+GvcMixerStream *
+gvc_mixer_sink_new (pa_context    *context,
+                    guint          index,
+                    GvcChannelMap *channel_map)
+
+{
+        GObject *object;
+
+        object = g_object_new (GVC_TYPE_MIXER_SINK,
+                               "pa-context", context,
+                               "index", index,
+                               "channel-map", channel_map,
+                               NULL);
+
+        return GVC_MIXER_STREAM (object);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-sink.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_SINK_H
+#define __GVC_MIXER_SINK_H
+
+#include <glib-object.h>
+#include "gvc-mixer-stream.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_SINK         (gvc_mixer_sink_get_type ())
+#define GVC_MIXER_SINK(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK, GvcMixerSink))
+#define GVC_MIXER_SINK_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass))
+#define GVC_IS_MIXER_SINK(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK))
+#define GVC_IS_MIXER_SINK_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK))
+#define GVC_MIXER_SINK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass))
+
+typedef struct GvcMixerSinkPrivate GvcMixerSinkPrivate;
+
+typedef struct
+{
+        GvcMixerStream       parent;
+        GvcMixerSinkPrivate *priv;
+} GvcMixerSink;
+
+typedef struct
+{
+        GvcMixerStreamClass parent_class;
+} GvcMixerSinkClass;
+
+GType               gvc_mixer_sink_get_type            (void);
+
+GvcMixerStream *    gvc_mixer_sink_new                 (pa_context    *context,
+                                                        guint          index,
+                                                        GvcChannelMap *map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SINK_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source-output.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source-output.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,128 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-source-output.h"
+
+#define GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputPrivate))
+
+struct GvcMixerSourceOutputPrivate
+{
+        gpointer dummy;
+};
+
+static void     gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass);
+static void     gvc_mixer_source_output_init       (GvcMixerSourceOutput      *mixer_source_output);
+static void     gvc_mixer_source_output_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcMixerSourceOutput, gvc_mixer_source_output, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_source_output_change_volume (GvcMixerStream *stream,
+                                       guint           volume)
+{
+        /* FIXME: */
+        return TRUE;
+}
+
+static gboolean
+gvc_mixer_source_output_change_is_muted (GvcMixerStream *stream,
+                                      gboolean        is_muted)
+{
+        /* FIXME: */
+        return TRUE;
+}
+
+static GObject *
+gvc_mixer_source_output_constructor (GType                  type,
+                                  guint                  n_construct_properties,
+                                  GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerSourceOutput *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_SOURCE_OUTPUT (object);
+
+        return object;
+}
+
+static void
+gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass)
+{
+        GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+        GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass);
+
+        object_class->constructor = gvc_mixer_source_output_constructor;
+        object_class->finalize = gvc_mixer_source_output_finalize;
+
+        stream_class->change_volume = gvc_mixer_source_output_change_volume;
+        stream_class->change_is_muted = gvc_mixer_source_output_change_is_muted;
+
+        g_type_class_add_private (klass, sizeof (GvcMixerSourceOutputPrivate));
+}
+
+static void
+gvc_mixer_source_output_init (GvcMixerSourceOutput *source_output)
+{
+        source_output->priv = GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE (source_output);
+
+}
+
+static void
+gvc_mixer_source_output_finalize (GObject *object)
+{
+        GvcMixerSourceOutput *mixer_source_output;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_SOURCE_OUTPUT (object));
+
+        mixer_source_output = GVC_MIXER_SOURCE_OUTPUT (object);
+
+        g_return_if_fail (mixer_source_output->priv != NULL);
+        G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->finalize (object);
+}
+
+GvcMixerStream *
+gvc_mixer_source_output_new (pa_context    *context,
+                             guint          index,
+                             GvcChannelMap *channel_map)
+{
+        GObject *object;
+
+        object = g_object_new (GVC_TYPE_MIXER_SOURCE_OUTPUT,
+                               "pa-context", context,
+                               "index", index,
+                               "channel-map", channel_map,
+                               NULL);
+
+        return GVC_MIXER_STREAM (object);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source-output.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source-output.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_SOURCE_OUTPUT_H
+#define __GVC_MIXER_SOURCE_OUTPUT_H
+
+#include <glib-object.h>
+#include "gvc-mixer-stream.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_SOURCE_OUTPUT         (gvc_mixer_source_output_get_type ())
+#define GVC_MIXER_SOURCE_OUTPUT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutput))
+#define GVC_MIXER_SOURCE_OUTPUT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass))
+#define GVC_IS_MIXER_SOURCE_OUTPUT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT))
+#define GVC_IS_MIXER_SOURCE_OUTPUT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE_OUTPUT))
+#define GVC_MIXER_SOURCE_OUTPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass))
+
+typedef struct GvcMixerSourceOutputPrivate GvcMixerSourceOutputPrivate;
+
+typedef struct
+{
+        GvcMixerStream               parent;
+        GvcMixerSourceOutputPrivate *priv;
+} GvcMixerSourceOutput;
+
+typedef struct
+{
+        GvcMixerStreamClass parent_class;
+} GvcMixerSourceOutputClass;
+
+GType               gvc_mixer_source_output_get_type      (void);
+
+GvcMixerStream *    gvc_mixer_source_output_new           (pa_context    *context,
+                                                           guint          index,
+                                                           GvcChannelMap *map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SOURCE_OUTPUT_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,188 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-source.h"
+
+#define GVC_MIXER_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourcePrivate))
+
+struct GvcMixerSourcePrivate
+{
+        gpointer dummy;
+};
+
+static void     gvc_mixer_source_class_init (GvcMixerSourceClass *klass);
+static void     gvc_mixer_source_init       (GvcMixerSource      *mixer_source);
+static void     gvc_mixer_source_finalize   (GObject            *object);
+
+G_DEFINE_TYPE (GvcMixerSource, gvc_mixer_source, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_source_change_volume (GvcMixerStream *stream,
+                              guint           volume)
+{
+        pa_operation      *o;
+        guint              index;
+        GvcChannelMap     *map;
+        pa_context        *context;
+        pa_cvolume         cv;
+        guint              num_channels;
+        guint              i;
+        gdouble           *gains;
+
+        index = gvc_mixer_stream_get_index (stream);
+
+        map = gvc_mixer_stream_get_channel_map (stream);
+        num_channels = gvc_channel_map_get_num_channels (map);
+        gains = gvc_channel_map_get_gains (map);
+
+        /* set all values to nominal level */
+        pa_cvolume_set (&cv, num_channels, (pa_volume_t)volume);
+
+
+        /* apply channel gain mapping */
+        for (i = 0; i < num_channels; i++) {
+                pa_volume_t v;
+                v = (double) volume * gains[i];
+                cv.values[i] = v;
+        }
+
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_source_volume_by_index (context,
+                                                   index,
+                                                   &cv,
+                                                   NULL,
+                                                   NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_source_volume_by_index() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static gboolean
+gvc_mixer_source_change_is_muted (GvcMixerStream *stream,
+                                gboolean        is_muted)
+{
+        pa_operation *o;
+        guint         index;
+        pa_context   *context;
+
+        index = gvc_mixer_stream_get_index (stream);
+        context = gvc_mixer_stream_get_pa_context (stream);
+
+        o = pa_context_set_source_mute_by_index (context,
+                                                 index,
+                                                 is_muted,
+                                                 NULL,
+                                                 NULL);
+
+        if (o == NULL) {
+                g_warning ("pa_context_set_source_mute_by_index() failed");
+                return FALSE;
+        }
+
+        pa_operation_unref(o);
+
+        return TRUE;
+}
+
+static GObject *
+gvc_mixer_source_constructor (GType                  type,
+                            guint                  n_construct_properties,
+                            GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerSource *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_source_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_SOURCE (object);
+
+        return object;
+}
+
+static void
+gvc_mixer_source_class_init (GvcMixerSourceClass *klass)
+{
+        GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+        GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass);
+
+        object_class->constructor = gvc_mixer_source_constructor;
+        object_class->finalize = gvc_mixer_source_finalize;
+
+        stream_class->change_volume = gvc_mixer_source_change_volume;
+        stream_class->change_is_muted = gvc_mixer_source_change_is_muted;
+
+        g_type_class_add_private (klass, sizeof (GvcMixerSourcePrivate));
+}
+
+static void
+gvc_mixer_source_init (GvcMixerSource *source)
+{
+        source->priv = GVC_MIXER_SOURCE_GET_PRIVATE (source);
+
+}
+
+static void
+gvc_mixer_source_finalize (GObject *object)
+{
+        GvcMixerSource *mixer_source;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_SOURCE (object));
+
+        mixer_source = GVC_MIXER_SOURCE (object);
+
+        g_return_if_fail (mixer_source->priv != NULL);
+        G_OBJECT_CLASS (gvc_mixer_source_parent_class)->finalize (object);
+}
+
+GvcMixerStream *
+gvc_mixer_source_new (pa_context    *context,
+                      guint          index,
+                      GvcChannelMap *channel_map)
+
+{
+        GObject *object;
+
+        object = g_object_new (GVC_TYPE_MIXER_SOURCE,
+                               "pa-context", context,
+                               "index", index,
+                               "channel-map", channel_map,
+                               NULL);
+
+        return GVC_MIXER_STREAM (object);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-source.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_SOURCE_H
+#define __GVC_MIXER_SOURCE_H
+
+#include <glib-object.h>
+#include "gvc-mixer-stream.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_SOURCE         (gvc_mixer_source_get_type ())
+#define GVC_MIXER_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSource))
+#define GVC_MIXER_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass))
+#define GVC_IS_MIXER_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE))
+#define GVC_IS_MIXER_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE))
+#define GVC_MIXER_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass))
+
+typedef struct GvcMixerSourcePrivate GvcMixerSourcePrivate;
+
+typedef struct
+{
+        GvcMixerStream       parent;
+        GvcMixerSourcePrivate *priv;
+} GvcMixerSource;
+
+typedef struct
+{
+        GvcMixerStreamClass parent_class;
+} GvcMixerSourceClass;
+
+GType               gvc_mixer_source_get_type            (void);
+
+GvcMixerStream *    gvc_mixer_source_new               (pa_context    *context,
+                                                        guint          index,
+                                                        GvcChannelMap *map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SOURCE_H */

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-stream.c	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,570 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-stream.h"
+
+#define GVC_MIXER_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamPrivate))
+
+static guint32 stream_serial = 1;
+
+struct GvcMixerStreamPrivate
+{
+        pa_context    *pa_context;
+        guint          id;
+        guint          index;
+        GvcChannelMap *channel_map;
+        guint          volume;
+        gdouble        decibel;
+        char          *name;
+        char          *description;
+        char          *icon_name;
+        gboolean       is_muted;
+        gboolean       can_decibel;
+};
+
+enum
+{
+        PROP_0,
+        PROP_ID,
+        PROP_PA_CONTEXT,
+        PROP_CHANNEL_MAP,
+        PROP_INDEX,
+        PROP_NAME,
+        PROP_DESCRIPTION,
+        PROP_ICON_NAME,
+        PROP_VOLUME,
+        PROP_DECIBEL,
+        PROP_IS_MUTED,
+        PROP_CAN_DECIBEL
+};
+
+static void     gvc_mixer_stream_class_init (GvcMixerStreamClass *klass);
+static void     gvc_mixer_stream_init       (GvcMixerStream      *mixer_stream);
+static void     gvc_mixer_stream_finalize   (GObject            *object);
+
+G_DEFINE_ABSTRACT_TYPE (GvcMixerStream, gvc_mixer_stream, G_TYPE_OBJECT)
+
+static guint32
+get_next_stream_serial (void)
+{
+        guint32 serial;
+
+        serial = stream_serial++;
+
+        if ((gint32)stream_serial < 0) {
+                stream_serial = 1;
+        }
+
+        return serial;
+}
+
+pa_context *
+gvc_mixer_stream_get_pa_context (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+        return stream->priv->pa_context;
+}
+
+guint
+gvc_mixer_stream_get_index (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+        return stream->priv->index;
+}
+
+guint
+gvc_mixer_stream_get_id (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+        return stream->priv->id;
+}
+
+GvcChannelMap *
+gvc_mixer_stream_get_channel_map (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+        return stream->priv->channel_map;
+}
+
+guint
+gvc_mixer_stream_get_volume (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+        return stream->priv->volume;
+}
+
+gdouble
+gvc_mixer_stream_get_decibel (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+        return stream->priv->decibel;
+}
+
+gboolean
+gvc_mixer_stream_set_volume (GvcMixerStream *stream,
+                             pa_volume_t     volume)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        if (volume != stream->priv->volume) {
+                stream->priv->volume = volume;
+                g_object_notify (G_OBJECT (stream), "volume");
+        }
+
+        return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_set_decibel (GvcMixerStream *stream,
+                              gdouble         db)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        if (db != stream->priv->decibel) {
+                stream->priv->decibel = db;
+                g_object_notify (G_OBJECT (stream), "decibel");
+        }
+
+        return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_get_is_muted  (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        return stream->priv->is_muted;
+}
+
+gboolean
+gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        return stream->priv->can_decibel;
+}
+
+gboolean
+gvc_mixer_stream_set_is_muted  (GvcMixerStream *stream,
+                                gboolean        is_muted)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        if (is_muted != stream->priv->is_muted) {
+                stream->priv->is_muted = is_muted;
+                g_object_notify (G_OBJECT (stream), "is-muted");
+        }
+
+        return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_set_can_decibel  (GvcMixerStream *stream,
+                                   gboolean        can_decibel)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        if (can_decibel != stream->priv->can_decibel) {
+                stream->priv->can_decibel = can_decibel;
+                g_object_notify (G_OBJECT (stream), "can-decibel");
+        }
+
+        return TRUE;
+}
+
+const char *
+gvc_mixer_stream_get_name (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+        return stream->priv->name;
+}
+
+const char *
+gvc_mixer_stream_get_description (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+        return stream->priv->description;
+}
+
+gboolean
+gvc_mixer_stream_set_name (GvcMixerStream *stream,
+                           const char     *name)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        g_free (stream->priv->name);
+        stream->priv->name = g_strdup (name);
+        g_object_notify (G_OBJECT (stream), "name");
+
+        return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_set_description (GvcMixerStream *stream,
+                                  const char     *description)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        g_free (stream->priv->description);
+        stream->priv->description = g_strdup (description);
+        g_object_notify (G_OBJECT (stream), "description");
+
+        return TRUE;
+}
+
+static void
+on_channel_map_gains_changed (GvcChannelMap  *channel_map,
+                              GvcMixerStream *stream)
+{
+        g_debug ("Gains changed");
+        gvc_mixer_stream_change_volume (stream, stream->priv->volume);
+}
+
+static gboolean
+gvc_mixer_stream_set_channel_map (GvcMixerStream *stream,
+                                  GvcChannelMap  *channel_map)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        if (channel_map != NULL) {
+                g_object_ref (channel_map);
+        }
+
+        if (stream->priv->channel_map != NULL) {
+                g_signal_handlers_disconnect_by_func (stream->priv->channel_map,
+                                                      on_channel_map_gains_changed,
+                                                      stream);
+                g_object_unref (stream->priv->channel_map);
+        }
+
+        stream->priv->channel_map = channel_map;
+
+        if (stream->priv->channel_map != NULL) {
+                g_signal_connect (stream->priv->channel_map,
+                                  "gains-changed",
+                                  G_CALLBACK (on_channel_map_gains_changed),
+                                  stream);
+
+                g_object_notify (G_OBJECT (stream), "channel-map");
+        }
+
+        return TRUE;
+}
+
+const char *
+gvc_mixer_stream_get_icon_name (GvcMixerStream *stream)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+        return stream->priv->icon_name;
+}
+
+gboolean
+gvc_mixer_stream_set_icon_name (GvcMixerStream *stream,
+                                const char     *icon_name)
+{
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+        g_free (stream->priv->icon_name);
+        stream->priv->icon_name = g_strdup (icon_name);
+        g_object_notify (G_OBJECT (stream), "icon-name");
+
+        return TRUE;
+}
+
+static void
+gvc_mixer_stream_set_property (GObject       *object,
+                               guint          prop_id,
+                               const GValue  *value,
+                               GParamSpec    *pspec)
+{
+        GvcMixerStream *self = GVC_MIXER_STREAM (object);
+
+        switch (prop_id) {
+        case PROP_PA_CONTEXT:
+                self->priv->pa_context = g_value_get_pointer (value);
+                break;
+        case PROP_INDEX:
+                self->priv->index = g_value_get_ulong (value);
+                break;
+        case PROP_ID:
+                self->priv->id = g_value_get_ulong (value);
+                break;
+        case PROP_CHANNEL_MAP:
+                gvc_mixer_stream_set_channel_map (self, g_value_get_object (value));
+                break;
+        case PROP_NAME:
+                gvc_mixer_stream_set_name (self, g_value_get_string (value));
+                break;
+        case PROP_DESCRIPTION:
+                gvc_mixer_stream_set_description (self, g_value_get_string (value));
+                break;
+        case PROP_ICON_NAME:
+                gvc_mixer_stream_set_icon_name (self, g_value_get_string (value));
+                break;
+        case PROP_VOLUME:
+                gvc_mixer_stream_set_volume (self, g_value_get_ulong (value));
+                break;
+        case PROP_DECIBEL:
+                gvc_mixer_stream_set_decibel (self, g_value_get_double (value));
+                break;
+        case PROP_IS_MUTED:
+                gvc_mixer_stream_set_is_muted (self, g_value_get_boolean (value));
+                break;
+        case PROP_CAN_DECIBEL:
+                gvc_mixer_stream_set_can_decibel (self, g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gvc_mixer_stream_get_property (GObject     *object,
+                               guint        prop_id,
+                               GValue      *value,
+                               GParamSpec  *pspec)
+{
+        GvcMixerStream *self = GVC_MIXER_STREAM (object);
+
+        switch (prop_id) {
+        case PROP_PA_CONTEXT:
+                g_value_set_pointer (value, self->priv->pa_context);
+                break;
+        case PROP_INDEX:
+                g_value_set_ulong (value, self->priv->index);
+                break;
+        case PROP_ID:
+                g_value_set_ulong (value, self->priv->id);
+                break;
+        case PROP_CHANNEL_MAP:
+                g_value_set_object (value, self->priv->channel_map);
+                break;
+        case PROP_NAME:
+                g_value_set_string (value, self->priv->name);
+                break;
+        case PROP_DESCRIPTION:
+                g_value_set_string (value, self->priv->description);
+                break;
+        case PROP_ICON_NAME:
+                g_value_set_string (value, self->priv->icon_name);
+                break;
+        case PROP_VOLUME:
+                g_value_set_ulong (value, self->priv->volume);
+                break;
+        case PROP_DECIBEL:
+                g_value_set_double (value, self->priv->decibel);
+                break;
+        case PROP_IS_MUTED:
+                g_value_set_boolean (value, self->priv->is_muted);
+                break;
+        case PROP_CAN_DECIBEL:
+                g_value_set_boolean (value, self->priv->can_decibel);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+gvc_mixer_stream_constructor (GType                  type,
+                              guint                  n_construct_properties,
+                              GObjectConstructParam *construct_params)
+{
+        GObject       *object;
+        GvcMixerStream *self;
+
+        object = G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+        self = GVC_MIXER_STREAM (object);
+
+        self->priv->id = get_next_stream_serial ();
+
+        return object;
+}
+
+static gboolean
+gvc_mixer_stream_real_change_volume (GvcMixerStream *stream,
+                                     guint           volume)
+{
+        return FALSE;
+}
+
+static gboolean
+gvc_mixer_stream_real_change_is_muted (GvcMixerStream *stream,
+                                       gboolean        is_muted)
+{
+        return FALSE;
+}
+
+gboolean
+gvc_mixer_stream_change_volume (GvcMixerStream *stream,
+                                guint           volume)
+{
+        gboolean ret;
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        ret = GVC_MIXER_STREAM_GET_CLASS (stream)->change_volume (stream, volume);
+        return ret;
+}
+
+gboolean
+gvc_mixer_stream_change_is_muted (GvcMixerStream *stream,
+                                  gboolean        is_muted)
+{
+        gboolean ret;
+        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+        ret = GVC_MIXER_STREAM_GET_CLASS (stream)->change_is_muted (stream, is_muted);
+        return ret;
+}
+
+static void
+gvc_mixer_stream_class_init (GvcMixerStreamClass *klass)
+{
+        GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
+
+        gobject_class->constructor = gvc_mixer_stream_constructor;
+        gobject_class->finalize = gvc_mixer_stream_finalize;
+        gobject_class->set_property = gvc_mixer_stream_set_property;
+        gobject_class->get_property = gvc_mixer_stream_get_property;
+
+        klass->change_volume = gvc_mixer_stream_real_change_volume;
+        klass->change_is_muted = gvc_mixer_stream_real_change_is_muted;
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_INDEX,
+                                         g_param_spec_ulong ("index",
+                                                             "Index",
+                                                             "The index for this stream",
+                                                             0, G_MAXULONG, 0,
+                                                             G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (gobject_class,
+                                         PROP_ID,
+                                         g_param_spec_ulong ("id",
+                                                             "id",
+                                                             "The id for this stream",
+                                                             0, G_MAXULONG, 0,
+                                                             G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (gobject_class,
+                                         PROP_CHANNEL_MAP,
+                                         g_param_spec_object ("channel-map",
+                                                              "channel map",
+                                                              "The channel map for this stream",
+                                                              GVC_TYPE_CHANNEL_MAP,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_PA_CONTEXT,
+                                         g_param_spec_pointer ("pa-context",
+                                                               "PulseAudio context",
+                                                               "The PulseAudio context for this stream",
+                                                               G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (gobject_class,
+                                         PROP_VOLUME,
+                                         g_param_spec_ulong ("volume",
+                                                             "Volume",
+                                                             "The volume for this stream",
+                                                             0, G_MAXULONG, 0,
+                                                             G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_DECIBEL,
+                                         g_param_spec_double ("decibel",
+                                                              "Decibel",
+                                                              "The decibel level for this stream",
+                                                              -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_NAME,
+                                         g_param_spec_string ("name",
+                                                              "Name",
+                                                              "Name to display for this stream",
+                                                              NULL,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_DESCRIPTION,
+                                         g_param_spec_string ("description",
+                                                              "Description",
+                                                              "Description to display for this stream",
+                                                              NULL,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_ICON_NAME,
+                                         g_param_spec_string ("icon-name",
+                                                              "Icon Name",
+                                                              "Name of icon to display for this stream",
+                                                              NULL,
+                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_IS_MUTED,
+                                         g_param_spec_boolean ("is-muted",
+                                                               "is muted",
+                                                               "Whether stream is muted",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+        g_object_class_install_property (gobject_class,
+                                         PROP_CAN_DECIBEL,
+                                         g_param_spec_boolean ("can-decibel",
+                                                               "can decibel",
+                                                               "Whether stream volume can be converted to decibel units",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+
+        g_type_class_add_private (klass, sizeof (GvcMixerStreamPrivate));
+}
+
+static void
+gvc_mixer_stream_init (GvcMixerStream *stream)
+{
+        stream->priv = GVC_MIXER_STREAM_GET_PRIVATE (stream);
+
+}
+
+static void
+gvc_mixer_stream_finalize (GObject *object)
+{
+        GvcMixerStream *mixer_stream;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GVC_IS_MIXER_STREAM (object));
+
+        mixer_stream = GVC_MIXER_STREAM (object);
+
+        g_return_if_fail (mixer_stream->priv != NULL);
+
+        g_free (mixer_stream->priv->name);
+        mixer_stream->priv->name = NULL;
+
+        g_free (mixer_stream->priv->description);
+        mixer_stream->priv->description = NULL;
+
+        g_free (mixer_stream->priv->icon_name);
+        mixer_stream->priv->icon_name = NULL;
+
+        G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->finalize (object);
+}

Added: trunk/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h
==============================================================================
--- (empty file)
+++ trunk/plugins/media-keys/cut-n-paste/gvc-mixer-stream.h	Thu Jan 15 22:11:25 2009
@@ -0,0 +1,95 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GVC_MIXER_STREAM_H
+#define __GVC_MIXER_STREAM_H
+
+#include <glib-object.h>
+#include <pulse/pulseaudio.h>
+
+#include "gvc-channel-map.h"
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_STREAM         (gvc_mixer_stream_get_type ())
+#define GVC_MIXER_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStream))
+#define GVC_MIXER_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass))
+#define GVC_IS_MIXER_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_STREAM))
+#define GVC_IS_MIXER_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_STREAM))
+#define GVC_MIXER_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass))
+
+typedef struct GvcMixerStreamPrivate GvcMixerStreamPrivate;
+
+typedef struct
+{
+        GObject                parent;
+        GvcMixerStreamPrivate *priv;
+} GvcMixerStream;
+
+typedef struct
+{
+        GObjectClass           parent_class;
+
+        /* vtable */
+        gboolean (*change_volume)   (GvcMixerStream *stream,
+                                     guint           volume);
+        gboolean (*change_is_muted) (GvcMixerStream *stream,
+                                     gboolean        is_muted);
+} GvcMixerStreamClass;
+
+GType               gvc_mixer_stream_get_type        (void);
+
+pa_context *        gvc_mixer_stream_get_pa_context  (GvcMixerStream *stream);
+guint               gvc_mixer_stream_get_index       (GvcMixerStream *stream);
+guint               gvc_mixer_stream_get_id          (GvcMixerStream *stream);
+GvcChannelMap *     gvc_mixer_stream_get_channel_map (GvcMixerStream *stream);
+
+guint               gvc_mixer_stream_get_volume      (GvcMixerStream *stream);
+gdouble             gvc_mixer_stream_get_decibel     (GvcMixerStream *stream);
+gboolean            gvc_mixer_stream_change_volume   (GvcMixerStream *stream,
+                                                      guint           volume);
+
+gboolean            gvc_mixer_stream_get_is_muted    (GvcMixerStream *stream);
+gboolean            gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream);
+gboolean            gvc_mixer_stream_change_is_muted (GvcMixerStream *stream,
+                                                      gboolean        is_muted);
+const char *        gvc_mixer_stream_get_name        (GvcMixerStream *stream);
+const char *        gvc_mixer_stream_get_icon_name   (GvcMixerStream *stream);
+const char *        gvc_mixer_stream_get_description (GvcMixerStream *stream);
+
+/* private */
+gboolean            gvc_mixer_stream_set_volume      (GvcMixerStream *stream,
+                                                      guint           volume);
+gboolean            gvc_mixer_stream_set_decibel     (GvcMixerStream *stream,
+                                                      gdouble         db);
+gboolean            gvc_mixer_stream_set_is_muted    (GvcMixerStream *stream,
+                                                      gboolean        is_muted);
+gboolean            gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream,
+                                                      gboolean        can_decibel);
+gboolean            gvc_mixer_stream_set_name        (GvcMixerStream *stream,
+                                                      const char     *name);
+gboolean            gvc_mixer_stream_set_description (GvcMixerStream *stream,
+                                                      const char     *description);
+gboolean            gvc_mixer_stream_set_icon_name   (GvcMixerStream *stream,
+                                                      const char     *name);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_STREAM_H */

Modified: trunk/plugins/media-keys/gsd-media-keys-manager.c
==============================================================================
--- trunk/plugins/media-keys/gsd-media-keys-manager.c	(original)
+++ trunk/plugins/media-keys/gsd-media-keys-manager.c	Thu Jan 15 22:11:25 2009
@@ -47,16 +47,20 @@
 #include "gsd-media-keys-manager-glue.h"
 
 #include "eggaccelerators.h"
-#include "actions/acme.h"
-#include "actions/acme-volume.h"
+#include "acme.h"
 #include "gsd-media-keys-window.h"
 
+#ifdef HAVE_PULSE
+#include "gvc-mixer-control.h"
+#endif /* HAVE_PULSE */
+
 #define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
 #define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
 #define GSD_MEDIA_KEYS_DBUS_PATH GSD_DBUS_PATH "/MediaKeys"
 #define GSD_MEDIA_KEYS_DBUS_NAME GSD_DBUS_NAME ".MediaKeys"
 
 #define VOLUME_STEP 6           /* percents for one volume button press */
+#define MAX_VOLUME 65536.0
 
 #if defined(__OpenBSD__)
 # define EJECT_COMMAND "eject -t /dev/cd0"
@@ -79,7 +83,14 @@
 
 struct GsdMediaKeysManagerPrivate
 {
-        AcmeVolume      *volume;
+#ifdef HAVE_PULSE
+        /* Volume bits */
+        GvcMixerControl *volume;
+        GvcMixerStream  *stream;
+        /* Number of expected update signals, zero meaning we
+         * shouldn't be showing any update dialogues */
+        guint            num_expected_update_signals;
+#endif /* HAVE_PULSE */
         GtkWidget       *dialog;
         GConfClient     *conf_client;
 
@@ -603,76 +614,159 @@
         g_free (command);
 }
 
+#ifdef HAVE_PULSE
+static void
+update_dialog (GsdMediaKeysManager *manager)
+{
+        gboolean muted;
+        guint vol;
+
+	/* Not expecting a dialogue to show up */
+        if (manager->priv->num_expected_update_signals == 0)
+                return;
+
+	/* If we aren't expecting any more updates, show the dialogue */
+        manager->priv->num_expected_update_signals--;
+        if (manager->priv->num_expected_update_signals != 0)
+                return;
+
+        vol = gvc_mixer_stream_get_volume (manager->priv->stream);
+        muted = gvc_mixer_stream_get_is_muted (manager->priv->stream);
+
+        dialog_init (manager);
+        gsd_media_keys_window_set_volume_muted (GSD_MEDIA_KEYS_WINDOW (manager->priv->dialog),
+                                                muted);
+        gsd_media_keys_window_set_volume_level (GSD_MEDIA_KEYS_WINDOW (manager->priv->dialog),
+                                                (int) (100 * (double)vol / PA_VOLUME_NORM));
+        gsd_media_keys_window_set_action (GSD_MEDIA_KEYS_WINDOW (manager->priv->dialog),
+                                          GSD_MEDIA_KEYS_WINDOW_ACTION_VOLUME);
+        dialog_show (manager);
+}
+
+static void
+on_stream_event_notify (GObject             *object,
+                        GParamSpec          *pspec,
+                        GsdMediaKeysManager *manager)
+{
+        update_dialog (manager);
+}
+
 static void
 do_sound_action (GsdMediaKeysManager *manager,
                  int                  type)
 {
         gboolean muted;
-        int      vol;
-        int      vol_step;
-        GError  *error = NULL;
+        guint vol, norm_vol_step;
+        int vol_step;
 
-        if (manager->priv->volume == NULL) {
+        if (manager->priv->stream == NULL)
                 return;
-        }
 
         vol_step = gconf_client_get_int (manager->priv->conf_client,
                                          GCONF_MISC_DIR "/volume_step",
-                                         &error);
+                                         NULL);
 
-        if (error) {
+        if (vol_step <= 0 || vol_step > 100)
                 vol_step = VOLUME_STEP;
-                g_error_free (error);
-        }
 
-        if (vol_step > 0) {
-                int threshold = acme_volume_get_threshold (manager->priv->volume);
-                if (vol_step < threshold)
-                        vol_step = threshold;
-                g_debug ("Using volume step of %d", vol_step);
-        }
+        norm_vol_step = PA_VOLUME_NORM * vol_step / 100;
 
         /* FIXME: this is racy */
-        vol = acme_volume_get_volume (manager->priv->volume);
-        muted = acme_volume_get_mute (manager->priv->volume);
+        vol = gvc_mixer_stream_get_volume (manager->priv->stream);
+        muted = gvc_mixer_stream_get_is_muted (manager->priv->stream);
+        /* By default, we would be showing a dialogue
+         * based on the current values, eg. an unchanged dialogue */
+        manager->priv->num_expected_update_signals = 0;
 
         switch (type) {
         case MUTE_KEY:
-                acme_volume_mute_toggle (manager->priv->volume);
+                manager->priv->num_expected_update_signals = 1;
+                gvc_mixer_stream_change_is_muted (manager->priv->stream, !muted);
                 break;
         case VOLUME_DOWN_KEY:
-                if (!muted && (vol <= vol_step)) {
-                        acme_volume_mute_toggle (manager->priv->volume);
+                if (!muted && (vol <= norm_vol_step)) {
+                        manager->priv->num_expected_update_signals = 2;
+                        gvc_mixer_stream_change_is_muted (manager->priv->stream, !muted);
+                        gvc_mixer_stream_change_volume (manager->priv->stream, 0);
+                } else if (!muted) {
+                        manager->priv->num_expected_update_signals = 1;
+                        gvc_mixer_stream_change_volume (manager->priv->stream, vol - norm_vol_step);
                 }
-                acme_volume_set_volume (manager->priv->volume, vol - vol_step);
                 break;
         case VOLUME_UP_KEY:
                 if (muted) {
                         if (vol == 0) {
-                                acme_volume_set_volume (manager->priv->volume, vol + vol_step);
+                                manager->priv->num_expected_update_signals = 2;
+                                gvc_mixer_stream_change_volume (manager->priv->stream, vol + norm_vol_step);
+                                gvc_mixer_stream_change_is_muted (manager->priv->stream, !muted);
+                        } else {
+                                manager->priv->num_expected_update_signals = 1;
+                                gvc_mixer_stream_change_is_muted (manager->priv->stream, !muted);
                         }
-                        acme_volume_mute_toggle (manager->priv->volume);
                 } else {
-                        acme_volume_set_volume (manager->priv->volume, vol + vol_step);
+                        if (vol < MAX_VOLUME) {
+                                manager->priv->num_expected_update_signals = 1;
+                                if (vol + norm_vol_step >= MAX_VOLUME) {
+                                        gvc_mixer_stream_change_volume (manager->priv->stream, MAX_VOLUME);
+                                } else {
+                                        gvc_mixer_stream_change_volume (manager->priv->stream, vol + norm_vol_step);
+                                }
+                        }
                 }
                 break;
         }
 
-        muted = acme_volume_get_mute (manager->priv->volume);
-        vol = acme_volume_get_volume (manager->priv->volume);
+        /* We didn't actually make any changes, so force showing the dialogue */
+        if (manager->priv->num_expected_update_signals == 0) {
+                manager->priv->num_expected_update_signals = 1;
+                update_dialog (manager);
+        }
+}
+
+static void
+update_default_sink (GsdMediaKeysManager *manager)
+{
+        GvcMixerStream *stream;
 
-        /* FIXME: AcmeVolume should probably emit signals
-           instead of doing it like this */
-        dialog_init (manager);
-        gsd_media_keys_window_set_volume_muted (GSD_MEDIA_KEYS_WINDOW (manager->priv->dialog),
-                                                muted);
-        gsd_media_keys_window_set_volume_level (GSD_MEDIA_KEYS_WINDOW (manager->priv->dialog),
-                                                vol);
-        gsd_media_keys_window_set_action (GSD_MEDIA_KEYS_WINDOW (manager->priv->dialog),
-                                          GSD_MEDIA_KEYS_WINDOW_ACTION_VOLUME);
-        dialog_show (manager);
+        stream = gvc_mixer_control_get_default_sink (manager->priv->volume);
+        if (stream == manager->priv->stream)
+                return;
+
+        if (manager->priv->stream != NULL) {
+                g_signal_handlers_disconnect_by_func (G_OBJECT (manager->priv->stream),
+                                                      G_CALLBACK (on_stream_event_notify), manager);
+                g_object_unref (manager->priv->stream);
+                manager->priv->stream = NULL;
+        }
+
+        if (stream != NULL) {
+                manager->priv->stream = stream;
+                g_signal_connect (G_OBJECT (manager->priv->stream), "notify::volume",
+                                  G_CALLBACK (on_stream_event_notify), manager);
+                g_signal_connect (G_OBJECT (manager->priv->stream), "notify::is-muted",
+                                  G_CALLBACK (on_stream_event_notify), manager);
+        } else {
+                g_warning ("Unable to get default sink");
+        }
+}
+
+static void
+on_control_ready (GvcMixerControl     *control,
+		  GsdMediaKeysManager *manager)
+{
+        update_default_sink (manager);
 }
 
+static void
+on_control_default_sink_changed (GvcMixerControl     *control,
+                                 guint                id,
+                                 GsdMediaKeysManager *manager)
+{
+        update_default_sink (manager);
+}
+
+#endif /* HAVE_PULSE */
+
 static gint
 find_by_application (gconstpointer a,
                      gconstpointer b)
@@ -793,7 +887,9 @@
         case MUTE_KEY:
         case VOLUME_DOWN_KEY:
         case VOLUME_UP_KEY:
+#ifdef HAVE_PULSE
                 do_sound_action (manager, type);
+#endif /* HAVE_PULSE */
                 break;
         case POWER_KEY:
                 do_exit_action (manager);
@@ -973,16 +1069,30 @@
 {
         gnome_settings_profile_start (NULL);
 
+#ifdef HAVE_PULSE
         /* initialise Volume handler
          *
          * We do this one here to force checking gstreamer cache, etc.
          * The rest (grabbing and setting the keys) can happen in an
          * idle.
          */
-        gnome_settings_profile_start ("acme_volume_new");
-        manager->priv->volume = acme_volume_new ();
-        gnome_settings_profile_end ("acme_volume_new");
+        gnome_settings_profile_start ("gvc_mixer_control_new");
+
+        manager->priv->volume = gvc_mixer_control_new ();
+
+        g_signal_connect (manager->priv->volume,
+                          "ready",
+                          G_CALLBACK (on_control_ready),
+                          manager);
+        g_signal_connect (manager->priv->volume,
+                          "default-sink-changed",
+                          G_CALLBACK (on_control_default_sink_changed),
+                          manager);
 
+        gvc_mixer_control_open (manager->priv->volume);
+
+        gnome_settings_profile_end ("gvc_mixer_control_new");
+#endif /* HAVE_PULSE */
         g_idle_add ((GSourceFunc) start_media_keys_idle_cb, manager);
 
         gnome_settings_profile_end (NULL);
@@ -1049,10 +1159,17 @@
         g_slist_free (priv->screens);
         priv->screens = NULL;
 
+#ifdef HAVE_PULSE
+        if (priv->stream) {
+                g_object_unref (priv->stream);
+                priv->stream = NULL;
+        }
+
         if (priv->volume) {
                 g_object_unref (priv->volume);
                 priv->volume = NULL;
         }
+#endif /* HAVE_PULSE */
 
         if (priv->dialog != NULL) {
                 gtk_widget_destroy (priv->dialog);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]