summaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--subprojects/gvc/.gitignore11
-rw-r--r--subprojects/gvc/.gitlab-ci.yml13
-rw-r--r--subprojects/gvc/.gitlab-ci/meson.build23
l---------subprojects/gvc/.gitlab-ci/subprojects/gvc1
-rw-r--r--subprojects/gvc/README.md12
-rw-r--r--subprojects/gvc/gvc-channel-map-private.h39
-rw-r--r--subprojects/gvc/gvc-channel-map.c247
-rw-r--r--subprojects/gvc/gvc-channel-map.h73
-rw-r--r--subprojects/gvc/gvc-mixer-card-private.h35
-rw-r--r--subprojects/gvc/gvc-mixer-card.c584
-rw-r--r--subprojects/gvc/gvc-mixer-card.h102
-rw-r--r--subprojects/gvc/gvc-mixer-control-private.h35
-rw-r--r--subprojects/gvc/gvc-mixer-control.c3885
-rw-r--r--subprojects/gvc/gvc-mixer-control.h155
-rw-r--r--subprojects/gvc/gvc-mixer-event-role.c228
-rw-r--r--subprojects/gvc/gvc-mixer-event-role.h57
-rw-r--r--subprojects/gvc/gvc-mixer-sink-input.c159
-rw-r--r--subprojects/gvc/gvc-mixer-sink-input.h57
-rw-r--r--subprojects/gvc/gvc-mixer-sink.c189
-rw-r--r--subprojects/gvc/gvc-mixer-sink.h57
-rw-r--r--subprojects/gvc/gvc-mixer-source-output.c160
-rw-r--r--subprojects/gvc/gvc-mixer-source-output.h57
-rw-r--r--subprojects/gvc/gvc-mixer-source.c189
-rw-r--r--subprojects/gvc/gvc-mixer-source.h57
-rw-r--r--subprojects/gvc/gvc-mixer-stream-private.h34
-rw-r--r--subprojects/gvc/gvc-mixer-stream.c1090
-rw-r--r--subprojects/gvc/gvc-mixer-stream.h146
-rw-r--r--subprojects/gvc/gvc-mixer-ui-device.c741
-rw-r--r--subprojects/gvc/gvc-mixer-ui-device.h85
-rw-r--r--subprojects/gvc/gvc-pulseaudio-fake.h30
-rw-r--r--subprojects/gvc/libgnome-volume-control.doap32
-rw-r--r--subprojects/gvc/meson.build137
-rw-r--r--subprojects/gvc/meson_options.txt41
-rw-r--r--subprojects/gvc/test-audio-device-selection.c84
-rw-r--r--subprojects/libadwaita.wrap3
35 files changed, 8848 insertions, 0 deletions
diff --git a/subprojects/gvc/.gitignore b/subprojects/gvc/.gitignore
new file mode 100644
index 0000000..7d2ebe7
--- /dev/null
+++ b/subprojects/gvc/.gitignore
@@ -0,0 +1,11 @@
+.deps/
+.libs/
+.dirstamp
+Makefile.in
+Makefile
+*.la
+*.lo
+*.o
+*.gir
+*.typelib
+test-audio-device-selection
diff --git a/subprojects/gvc/.gitlab-ci.yml b/subprojects/gvc/.gitlab-ci.yml
new file mode 100644
index 0000000..ad894f0
--- /dev/null
+++ b/subprojects/gvc/.gitlab-ci.yml
@@ -0,0 +1,13 @@
+stages:
+- test
+
+build-fedora:
+ image: fedora:latest
+ stage: test
+ before_script:
+ - dnf install -y redhat-rpm-config gcc meson pulseaudio-libs-devel alsa-lib-devel gtk3-devel
+ script:
+ - cd .gitlab-ci
+ - meson _build
+ - ninja -C _build
+
diff --git a/subprojects/gvc/.gitlab-ci/meson.build b/subprojects/gvc/.gitlab-ci/meson.build
new file mode 100644
index 0000000..d54e1dd
--- /dev/null
+++ b/subprojects/gvc/.gitlab-ci/meson.build
@@ -0,0 +1,23 @@
+project('gnome-volume-control-ci', 'c',
+ version: '1.0.0',
+ meson_version: '>= 0.47.0',
+ license: 'GPLv2+'
+)
+
+prefix = get_option('prefix')
+
+datadir = join_paths(prefix, get_option('datadir'))
+libdir = join_paths(prefix, get_option('libdir'))
+
+pkgdatadir = join_paths(datadir, meson.project_name())
+pkglibdir = join_paths(libdir, meson.project_name())
+
+libgvc = subproject('gvc',
+ default_options: [
+ 'package_name=' + meson.project_name(),
+ 'package_version=' + meson.project_version(),
+ 'pkgdatadir=' + pkgdatadir,
+ 'pkglibdir=' + pkglibdir,
+ 'alsa=true'
+ ]
+)
diff --git a/subprojects/gvc/.gitlab-ci/subprojects/gvc b/subprojects/gvc/.gitlab-ci/subprojects/gvc
new file mode 120000
index 0000000..6581736
--- /dev/null
+++ b/subprojects/gvc/.gitlab-ci/subprojects/gvc
@@ -0,0 +1 @@
+../../ \ No newline at end of file
diff --git a/subprojects/gvc/README.md b/subprojects/gvc/README.md
new file mode 100644
index 0000000..2fabe49
--- /dev/null
+++ b/subprojects/gvc/README.md
@@ -0,0 +1,12 @@
+# libgnome-volume-control
+
+libgnome-volume-control is a copy library that's supposed to be used as
+a git sub-module. If your project uses some of libgnome-volume-control's
+strings in a user-facing manner, don't forget to add those files to your
+POTFILES.in for translation.
+
+## Projects using libgnome-volume-control
+
+- [gnome-shell](https://gitlab.gnome.org/GNOME/gnome-shell)
+- [gnome-settings-daemon](https://gitlab.gnome.org/GNOME/gnome-settings-daemon)
+- [gnome-control-center](https://gitlab.gnome.org/GNOME/gnome-control-center)
diff --git a/subprojects/gvc/gvc-channel-map-private.h b/subprojects/gvc/gvc-channel-map-private.h
new file mode 100644
index 0000000..3949de3
--- /dev/null
+++ b/subprojects/gvc/gvc-channel-map-private.h
@@ -0,0 +1,39 @@
+/* -*- 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_PRIVATE_H
+#define __GVC_CHANNEL_MAP_PRIVATE_H
+
+#include <glib-object.h>
+#include <pulse/pulseaudio.h>
+
+G_BEGIN_DECLS
+
+GvcChannelMap * gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *map);
+const pa_channel_map * gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map);
+
+void gvc_channel_map_volume_changed (GvcChannelMap *map,
+ const pa_cvolume *cv,
+ gboolean set);
+const pa_cvolume * gvc_channel_map_get_cvolume (const GvcChannelMap *map);
+
+G_END_DECLS
+
+#endif /* __GVC_CHANNEL_MAP_PRIVATE_H */
diff --git a/subprojects/gvc/gvc-channel-map.c b/subprojects/gvc/gvc-channel-map.c
new file mode 100644
index 0000000..bf4d737
--- /dev/null
+++ b/subprojects/gvc/gvc-channel-map.c
@@ -0,0 +1,247 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-channel-map.h"
+#include "gvc-channel-map-private.h"
+
+struct GvcChannelMapPrivate
+{
+ pa_channel_map pa_map;
+ gboolean pa_volume_is_set;
+ pa_cvolume pa_volume;
+ gdouble extern_volume[NUM_TYPES]; /* volume, balance, fade, lfe */
+ gboolean can_balance;
+ gboolean can_fade;
+};
+
+enum {
+ VOLUME_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void gvc_channel_map_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcChannelMap, gvc_channel_map, G_TYPE_OBJECT)
+
+guint
+gvc_channel_map_get_num_channels (const GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), 0);
+
+ if (!pa_channel_map_valid(&map->priv->pa_map))
+ return 0;
+
+ return map->priv->pa_map.channels;
+}
+
+const gdouble *
+gvc_channel_map_get_volume (GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL);
+
+ if (!pa_channel_map_valid(&map->priv->pa_map))
+ return NULL;
+
+ map->priv->extern_volume[VOLUME] = (gdouble) pa_cvolume_max (&map->priv->pa_volume);
+ if (gvc_channel_map_can_balance (map))
+ map->priv->extern_volume[BALANCE] = (gdouble) pa_cvolume_get_balance (&map->priv->pa_volume, &map->priv->pa_map);
+ else
+ map->priv->extern_volume[BALANCE] = 0;
+ if (gvc_channel_map_can_fade (map))
+ map->priv->extern_volume[FADE] = (gdouble) pa_cvolume_get_fade (&map->priv->pa_volume, &map->priv->pa_map);
+ else
+ map->priv->extern_volume[FADE] = 0;
+ if (gvc_channel_map_has_lfe (map))
+ map->priv->extern_volume[LFE] = (gdouble) pa_cvolume_get_position (&map->priv->pa_volume, &map->priv->pa_map, PA_CHANNEL_POSITION_LFE);
+ else
+ map->priv->extern_volume[LFE] = 0;
+
+ return map->priv->extern_volume;
+}
+
+gboolean
+gvc_channel_map_can_balance (const GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE);
+
+ return map->priv->can_balance;
+}
+
+gboolean
+gvc_channel_map_can_fade (const GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE);
+
+ return map->priv->can_fade;
+}
+
+const char *
+gvc_channel_map_get_mapping (const GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL);
+
+ if (!pa_channel_map_valid(&map->priv->pa_map))
+ return NULL;
+
+ return pa_channel_map_to_pretty_name (&map->priv->pa_map);
+}
+
+/**
+ * gvc_channel_map_has_position: (skip)
+ * @map:
+ * @position:
+ *
+ * Returns:
+ */
+gboolean
+gvc_channel_map_has_position (const GvcChannelMap *map,
+ pa_channel_position_t position)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE);
+
+ return pa_channel_map_has_position (&(map->priv->pa_map), position);
+}
+
+const pa_channel_map *
+gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL);
+
+ if (!pa_channel_map_valid(&map->priv->pa_map))
+ return NULL;
+
+ return &map->priv->pa_map;
+}
+
+const pa_cvolume *
+gvc_channel_map_get_cvolume (const GvcChannelMap *map)
+{
+ g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL);
+
+ if (!pa_channel_map_valid(&map->priv->pa_map))
+ return NULL;
+
+ return &map->priv->pa_volume;
+}
+
+static void
+gvc_channel_map_class_init (GvcChannelMapClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gvc_channel_map_finalize;
+
+ signals [VOLUME_CHANGED] =
+ g_signal_new ("volume-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcChannelMapClass, volume_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+}
+
+void
+gvc_channel_map_volume_changed (GvcChannelMap *map,
+ const pa_cvolume *cv,
+ gboolean set)
+{
+ g_return_if_fail (GVC_IS_CHANNEL_MAP (map));
+ g_return_if_fail (cv != NULL);
+ g_return_if_fail (pa_cvolume_compatible_with_channel_map(cv, &map->priv->pa_map));
+
+ if (pa_cvolume_equal(cv, &map->priv->pa_volume))
+ return;
+
+ map->priv->pa_volume = *cv;
+
+ if (map->priv->pa_volume_is_set == FALSE) {
+ map->priv->pa_volume_is_set = TRUE;
+ return;
+ }
+ g_signal_emit (map, signals[VOLUME_CHANGED], 0, set);
+}
+
+static void
+gvc_channel_map_init (GvcChannelMap *map)
+{
+ map->priv = gvc_channel_map_get_instance_private (map);
+ map->priv->pa_volume_is_set = FALSE;
+}
+
+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)
+{
+ g_assert (pa_channel_map_valid(pa_map));
+
+ map->priv->can_balance = pa_channel_map_can_balance (pa_map);
+ map->priv->can_fade = pa_channel_map_can_fade (pa_map);
+
+ map->priv->pa_map = *pa_map;
+ pa_cvolume_set(&map->priv->pa_volume, pa_map->channels, PA_VOLUME_NORM);
+}
+
+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);
+}
diff --git a/subprojects/gvc/gvc-channel-map.h b/subprojects/gvc/gvc-channel-map.h
new file mode 100644
index 0000000..85c5772
--- /dev/null
+++ b/subprojects/gvc/gvc-channel-map.h
@@ -0,0 +1,73 @@
+/* -*- 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 <gvc-pulseaudio-fake.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 (*volume_changed) (GvcChannelMap *channel_map, gboolean set);
+} GvcChannelMapClass;
+
+enum {
+ VOLUME,
+ BALANCE,
+ FADE,
+ LFE,
+ NUM_TYPES
+};
+
+GType gvc_channel_map_get_type (void);
+
+GvcChannelMap * gvc_channel_map_new (void);
+guint gvc_channel_map_get_num_channels (const GvcChannelMap *map);
+const gdouble * gvc_channel_map_get_volume (GvcChannelMap *map);
+gboolean gvc_channel_map_can_balance (const GvcChannelMap *map);
+gboolean gvc_channel_map_can_fade (const GvcChannelMap *map);
+gboolean gvc_channel_map_has_position (const GvcChannelMap *map,
+ pa_channel_position_t position);
+#define gvc_channel_map_has_lfe(x) gvc_channel_map_has_position (x, PA_CHANNEL_POSITION_LFE)
+
+const char * gvc_channel_map_get_mapping (const GvcChannelMap *map);
+
+G_END_DECLS
+
+#endif /* __GVC_CHANNEL_MAP_H */
diff --git a/subprojects/gvc/gvc-mixer-card-private.h b/subprojects/gvc/gvc-mixer-card-private.h
new file mode 100644
index 0000000..e190f7f
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-card-private.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008-2009 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_CARD_PRIVATE_H
+#define __GVC_MIXER_CARD_PRIVATE_H
+
+#include <pulse/pulseaudio.h>
+#include "gvc-mixer-card.h"
+
+G_BEGIN_DECLS
+
+GvcMixerCard * gvc_mixer_card_new (pa_context *context,
+ guint index);
+pa_context * gvc_mixer_card_get_pa_context (GvcMixerCard *card);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_CARD_PRIVATE_H */
diff --git a/subprojects/gvc/gvc-mixer-card.c b/subprojects/gvc/gvc-mixer-card.c
new file mode 100644
index 0000000..93be4da
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-card.c
@@ -0,0 +1,584 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann
+ * Copyright (C) 2009 Bastien Nocera
+ * Copyright (C) Conor Curran 2011 <conor.curran@canonical.com>
+ *
+ * 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-card.h"
+#include "gvc-mixer-card-private.h"
+
+static guint32 card_serial = 1;
+
+struct GvcMixerCardPrivate
+{
+ pa_context *pa_context;
+ guint id;
+ guint index;
+ char *name;
+ char *icon_name;
+ char *profile;
+ char *target_profile;
+ char *human_profile;
+ GList *profiles;
+ pa_operation *profile_op;
+ GList *ports;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ID,
+ PROP_PA_CONTEXT,
+ PROP_INDEX,
+ PROP_NAME,
+ PROP_ICON_NAME,
+ PROP_PROFILE,
+ PROP_HUMAN_PROFILE,
+};
+
+static void gvc_mixer_card_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerCard, gvc_mixer_card, G_TYPE_OBJECT)
+
+static guint32
+get_next_card_serial (void)
+{
+ guint32 serial;
+
+ serial = card_serial++;
+
+ if ((gint32)card_serial < 0) {
+ card_serial = 1;
+ }
+
+ return serial;
+}
+
+pa_context *
+gvc_mixer_card_get_pa_context (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0);
+ return card->priv->pa_context;
+}
+
+guint
+gvc_mixer_card_get_index (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0);
+ return card->priv->index;
+}
+
+guint
+gvc_mixer_card_get_id (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0);
+ return card->priv->id;
+}
+
+const char *
+gvc_mixer_card_get_name (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+ return card->priv->name;
+}
+
+gboolean
+gvc_mixer_card_set_name (GvcMixerCard *card,
+ const char *name)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+
+ g_free (card->priv->name);
+ card->priv->name = g_strdup (name);
+ g_object_notify (G_OBJECT (card), "name");
+
+ return TRUE;
+}
+
+const char *
+gvc_mixer_card_get_icon_name (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+ return card->priv->icon_name;
+}
+
+gboolean
+gvc_mixer_card_set_icon_name (GvcMixerCard *card,
+ const char *icon_name)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+
+ g_free (card->priv->icon_name);
+ card->priv->icon_name = g_strdup (icon_name);
+ g_object_notify (G_OBJECT (card), "icon-name");
+
+ return TRUE;
+}
+
+/**
+ * gvc_mixer_card_get_profile: (skip)
+ * @card:
+ *
+ * Returns:
+ */
+GvcMixerCardProfile *
+gvc_mixer_card_get_profile (GvcMixerCard *card)
+{
+ GList *l;
+
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+ g_return_val_if_fail (card->priv->profiles != NULL, NULL);
+
+ for (l = card->priv->profiles; l != NULL; l = l->next) {
+ GvcMixerCardProfile *p = l->data;
+ if (g_str_equal (card->priv->profile, p->profile)) {
+ return p;
+ }
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+gboolean
+gvc_mixer_card_set_profile (GvcMixerCard *card,
+ const char *profile)
+{
+ GList *l;
+
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+ g_return_val_if_fail (card->priv->profiles != NULL, FALSE);
+
+ g_free (card->priv->profile);
+ card->priv->profile = g_strdup (profile);
+
+ g_free (card->priv->human_profile);
+ card->priv->human_profile = NULL;
+
+ for (l = card->priv->profiles; l != NULL; l = l->next) {
+ GvcMixerCardProfile *p = l->data;
+ if (g_str_equal (card->priv->profile, p->profile)) {
+ card->priv->human_profile = g_strdup (p->human_profile);
+ break;
+ }
+ }
+
+ g_object_notify (G_OBJECT (card), "profile");
+
+ return TRUE;
+}
+
+static void
+_pa_context_set_card_profile_by_index_cb (pa_context *context,
+ int success,
+ void *userdata)
+{
+ GvcMixerCard *card = GVC_MIXER_CARD (userdata);
+
+ g_assert (card->priv->target_profile);
+
+ if (success > 0) {
+ gvc_mixer_card_set_profile (card, card->priv->target_profile);
+ } else {
+ g_debug ("Failed to switch profile on '%s' from '%s' to '%s'",
+ card->priv->name,
+ card->priv->profile,
+ card->priv->target_profile);
+ }
+ g_free (card->priv->target_profile);
+ card->priv->target_profile = NULL;
+
+ pa_operation_unref (card->priv->profile_op);
+ card->priv->profile_op = NULL;
+}
+
+/**
+ * gvc_mixer_card_change_profile:
+ * @card: a #GvcMixerCard
+ * @profile: (allow-none): the profile to change to or %NULL.
+ *
+ * Change the profile in use on this card.
+ *
+ * Returns: %TRUE if profile successfully changed or already using this profile.
+ */
+gboolean
+gvc_mixer_card_change_profile (GvcMixerCard *card,
+ const char *profile)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+ g_return_val_if_fail (card->priv->profiles != NULL, FALSE);
+
+ /* Same profile, or already requested? */
+ if (g_strcmp0 (card->priv->profile, profile) == 0)
+ return TRUE;
+ if (g_strcmp0 (profile, card->priv->target_profile) == 0)
+ return TRUE;
+ if (card->priv->profile_op != NULL) {
+ pa_operation_cancel (card->priv->profile_op);
+ pa_operation_unref (card->priv->profile_op);
+ card->priv->profile_op = NULL;
+ }
+
+ if (card->priv->profile != NULL) {
+ g_free (card->priv->target_profile);
+ card->priv->target_profile = g_strdup (profile);
+
+ card->priv->profile_op = pa_context_set_card_profile_by_index (card->priv->pa_context,
+ card->priv->index,
+ card->priv->target_profile,
+ _pa_context_set_card_profile_by_index_cb,
+ card);
+
+ if (card->priv->profile_op == NULL) {
+ g_warning ("pa_context_set_card_profile_by_index() failed");
+ return FALSE;
+ }
+ } else {
+ g_assert (card->priv->human_profile == NULL);
+ card->priv->profile = g_strdup (profile);
+ }
+
+ return TRUE;
+}
+
+/**
+ * gvc_mixer_card_get_profiles:
+ *
+ * Return value: (transfer none) (element-type GvcMixerCardProfile):
+ */
+const GList *
+gvc_mixer_card_get_profiles (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+ return card->priv->profiles;
+}
+
+/**
+ * gvc_mixer_card_get_ports:
+ *
+ * Return value: (transfer none) (element-type GvcMixerCardPort):
+ */
+const GList *
+gvc_mixer_card_get_ports (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+ return card->priv->ports;
+}
+
+/**
+ * gvc_mixer_card_profile_compare:
+ *
+ * Return value: 1 if @a has a higher priority, -1 if @b has a higher
+ * priority, 0 if @a and @b have the same priority.
+ */
+int
+gvc_mixer_card_profile_compare (GvcMixerCardProfile *a,
+ GvcMixerCardProfile *b)
+{
+ if (a->priority == b->priority)
+ return 0;
+ if (a->priority > b->priority)
+ return 1;
+ return -1;
+}
+
+/**
+ * gvc_mixer_card_set_profiles:
+ * @profiles: (transfer full) (element-type GvcMixerCardProfile):
+ */
+gboolean
+gvc_mixer_card_set_profiles (GvcMixerCard *card,
+ GList *profiles)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+ g_return_val_if_fail (card->priv->profiles == NULL, FALSE);
+
+ card->priv->profiles = g_list_sort (profiles, (GCompareFunc) gvc_mixer_card_profile_compare);
+
+ return TRUE;
+}
+
+/**
+ * gvc_mixer_card_get_gicon:
+ * @card:
+ *
+ * Return value: (transfer full):
+ */
+GIcon *
+gvc_mixer_card_get_gicon (GvcMixerCard *card)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL);
+
+ if (card->priv->icon_name == NULL)
+ return NULL;
+
+ return g_themed_icon_new_with_default_fallbacks (card->priv->icon_name);
+}
+
+static void
+free_port (GvcMixerCardPort *port)
+{
+ g_free (port->port);
+ g_free (port->human_port);
+ g_free (port->icon_name);
+ g_list_free (port->profiles);
+
+ g_free (port);
+}
+
+/**
+ * gvc_mixer_card_set_ports:
+ * @ports: (transfer full) (element-type GvcMixerCardPort):
+ */
+gboolean
+gvc_mixer_card_set_ports (GvcMixerCard *card,
+ GList *ports)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);
+ g_return_val_if_fail (card->priv->ports == NULL, FALSE);
+
+ g_list_free_full (card->priv->ports, (GDestroyNotify) free_port);
+ card->priv->ports = ports;
+
+ return TRUE;
+}
+
+static void
+gvc_mixer_card_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GvcMixerCard *self = GVC_MIXER_CARD (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_NAME:
+ gvc_mixer_card_set_name (self, g_value_get_string (value));
+ break;
+ case PROP_ICON_NAME:
+ gvc_mixer_card_set_icon_name (self, g_value_get_string (value));
+ break;
+ case PROP_PROFILE:
+ gvc_mixer_card_set_profile (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gvc_mixer_card_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GvcMixerCard *self = GVC_MIXER_CARD (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_NAME:
+ g_value_set_string (value, self->priv->name);
+ break;
+ case PROP_ICON_NAME:
+ g_value_set_string (value, self->priv->icon_name);
+ break;
+ case PROP_PROFILE:
+ g_value_set_string (value, self->priv->profile);
+ break;
+ case PROP_HUMAN_PROFILE:
+ g_value_set_string (value, self->priv->human_profile);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+gvc_mixer_card_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ GvcMixerCard *self;
+
+ object = G_OBJECT_CLASS (gvc_mixer_card_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+ self = GVC_MIXER_CARD (object);
+
+ self->priv->id = get_next_card_serial ();
+
+ return object;
+}
+
+static void
+gvc_mixer_card_class_init (GvcMixerCardClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructor = gvc_mixer_card_constructor;
+ gobject_class->finalize = gvc_mixer_card_finalize;
+
+ gobject_class->set_property = gvc_mixer_card_set_property;
+ gobject_class->get_property = gvc_mixer_card_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_INDEX,
+ g_param_spec_ulong ("index",
+ "Index",
+ "The index for this card",
+ 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 card",
+ 0, G_MAXULONG, 0,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (gobject_class,
+ PROP_PA_CONTEXT,
+ g_param_spec_pointer ("pa-context",
+ "PulseAudio context",
+ "The PulseAudio context for this card",
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (gobject_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "Name to display for this card",
+ 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 card",
+ NULL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+ g_object_class_install_property (gobject_class,
+ PROP_PROFILE,
+ g_param_spec_string ("profile",
+ "Profile",
+ "Name of current profile for this card",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_HUMAN_PROFILE,
+ g_param_spec_string ("human-profile",
+ "Profile (Human readable)",
+ "Name of current profile for this card in human readable form",
+ NULL,
+ G_PARAM_READABLE));
+}
+
+static void
+gvc_mixer_card_init (GvcMixerCard *card)
+{
+ card->priv = gvc_mixer_card_get_instance_private (card);
+}
+
+GvcMixerCard *
+gvc_mixer_card_new (pa_context *context,
+ guint index)
+{
+ GObject *object;
+
+ object = g_object_new (GVC_TYPE_MIXER_CARD,
+ "index", index,
+ "pa-context", context,
+ NULL);
+ return GVC_MIXER_CARD (object);
+}
+
+static void
+free_profile (GvcMixerCardProfile *p)
+{
+ g_free (p->profile);
+ g_free (p->human_profile);
+ g_free (p->status);
+ g_free (p);
+}
+
+static void
+gvc_mixer_card_finalize (GObject *object)
+{
+ GvcMixerCard *mixer_card;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GVC_IS_MIXER_CARD (object));
+
+ mixer_card = GVC_MIXER_CARD (object);
+
+ g_return_if_fail (mixer_card->priv != NULL);
+
+ g_free (mixer_card->priv->name);
+ mixer_card->priv->name = NULL;
+
+ g_free (mixer_card->priv->icon_name);
+ mixer_card->priv->icon_name = NULL;
+
+ g_free (mixer_card->priv->target_profile);
+ mixer_card->priv->target_profile = NULL;
+
+ g_free (mixer_card->priv->profile);
+ mixer_card->priv->profile = NULL;
+
+ g_free (mixer_card->priv->human_profile);
+ mixer_card->priv->human_profile = NULL;
+
+ g_list_free_full (mixer_card->priv->profiles, (GDestroyNotify) free_profile);
+ mixer_card->priv->profiles = NULL;
+
+ g_list_free_full (mixer_card->priv->ports, (GDestroyNotify) free_port);
+ mixer_card->priv->ports = NULL;
+
+ G_OBJECT_CLASS (gvc_mixer_card_parent_class)->finalize (object);
+}
+
diff --git a/subprojects/gvc/gvc-mixer-card.h b/subprojects/gvc/gvc-mixer-card.h
new file mode 100644
index 0000000..814f8d4
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-card.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ * Copyright (C) Conor Curran 2011 <conor.curran@canonical.com>
+ *
+ * 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_CARD_H
+#define __GVC_MIXER_CARD_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_CARD (gvc_mixer_card_get_type ())
+#define GVC_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CARD, GvcMixerCard))
+#define GVC_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CARD, GvcMixerCardClass))
+#define GVC_IS_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CARD))
+#define GVC_IS_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CARD))
+#define GVC_MIXER_CARD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardClass))
+
+typedef struct GvcMixerCardPrivate GvcMixerCardPrivate;
+
+typedef struct
+{
+ GObject parent;
+ GvcMixerCardPrivate *priv;
+} GvcMixerCard;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ /* vtable */
+} GvcMixerCardClass;
+
+typedef struct
+{
+ char *profile;
+ char *human_profile;
+ char *status;
+ guint priority;
+ guint n_sinks, n_sources;
+} GvcMixerCardProfile;
+
+typedef struct
+{
+ char *port;
+ char *human_port;
+ char *icon_name;
+ guint priority;
+ gint available;
+ gint direction;
+ GList *profiles;
+} GvcMixerCardPort;
+
+GType gvc_mixer_card_get_type (void);
+
+guint gvc_mixer_card_get_id (GvcMixerCard *card);
+guint gvc_mixer_card_get_index (GvcMixerCard *card);
+const char * gvc_mixer_card_get_name (GvcMixerCard *card);
+const char * gvc_mixer_card_get_icon_name (GvcMixerCard *card);
+GvcMixerCardProfile * gvc_mixer_card_get_profile (GvcMixerCard *card);
+const GList * gvc_mixer_card_get_profiles (GvcMixerCard *card);
+const GList * gvc_mixer_card_get_ports (GvcMixerCard *card);
+gboolean gvc_mixer_card_change_profile (GvcMixerCard *card,
+ const char *profile);
+GIcon * gvc_mixer_card_get_gicon (GvcMixerCard *card);
+
+int gvc_mixer_card_profile_compare (GvcMixerCardProfile *a,
+ GvcMixerCardProfile *b);
+
+/* private */
+gboolean gvc_mixer_card_set_name (GvcMixerCard *card,
+ const char *name);
+gboolean gvc_mixer_card_set_icon_name (GvcMixerCard *card,
+ const char *name);
+gboolean gvc_mixer_card_set_profile (GvcMixerCard *card,
+ const char *profile);
+gboolean gvc_mixer_card_set_profiles (GvcMixerCard *card,
+ GList *profiles);
+gboolean gvc_mixer_card_set_ports (GvcMixerCard *stream,
+ GList *ports);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_CARD_H */
diff --git a/subprojects/gvc/gvc-mixer-control-private.h b/subprojects/gvc/gvc-mixer-control-private.h
new file mode 100644
index 0000000..ac79975
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-control-private.h
@@ -0,0 +1,35 @@
+/* -*- 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_PRIVATE_H
+#define __GVC_MIXER_CONTROL_PRIVATE_H
+
+#include <glib-object.h>
+#include <pulse/pulseaudio.h>
+#include "gvc-mixer-stream.h"
+#include "gvc-mixer-card.h"
+
+G_BEGIN_DECLS
+
+pa_context * gvc_mixer_control_get_pa_context (GvcMixerControl *control);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_CONTROL_PRIVATE_H */
diff --git a/subprojects/gvc/gvc-mixer-control.c b/subprojects/gvc/gvc-mixer-control.c
new file mode 100644
index 0000000..6218a1b
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-control.c
@@ -0,0 +1,3885 @@
+/* -*- 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
+ * Copyright (C) 2012 Conor Curran
+ *
+ * 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/glib-mainloop.h>
+#include <pulse/ext-stream-restore.h>
+
+#ifdef HAVE_ALSA
+#include <alsa/asoundlib.h>
+#endif /* HAVE_ALSA */
+
+#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"
+#include "gvc-mixer-card.h"
+#include "gvc-mixer-card-private.h"
+#include "gvc-channel-map-private.h"
+#include "gvc-mixer-control-private.h"
+#include "gvc-mixer-ui-device.h"
+
+#define RECONNECT_DELAY 5
+
+enum {
+ PROP_0,
+ PROP_NAME
+};
+
+struct GvcMixerControlPrivate
+{
+ pa_glib_mainloop *pa_mainloop;
+ pa_mainloop_api *pa_api;
+ pa_context *pa_context;
+ guint server_protocol_version;
+ int n_outstanding;
+ guint reconnect_id;
+ char *name;
+
+ 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;
+ GHashTable *cards;
+
+ GvcMixerStream *new_default_sink_stream; /* new default sink stream, used in gvc_mixer_control_set_default_sink () */
+ GvcMixerStream *new_default_source_stream; /* new default source stream, used in gvc_mixer_control_set_default_source () */
+
+ GHashTable *ui_outputs; /* UI visible outputs */
+ GHashTable *ui_inputs; /* UI visible inputs */
+
+ /* When we change profile on a device that is not the server default sink,
+ * it will jump back to the default sink set by the server to prevent the
+ * audio setup from being 'outputless'.
+ *
+ * All well and good but then when we get the new stream created for the
+ * new profile how do we know that this is the intended default or selected
+ * device the user wishes to use. */
+ guint profile_swapping_device_id;
+
+#ifdef HAVE_ALSA
+ int headset_card;
+ gboolean has_headsetmic;
+ gboolean has_headphonemic;
+ gboolean headset_plugged_in;
+ char *headphones_name;
+ char *headsetmic_name;
+ char *headphonemic_name;
+ char *internalspk_name;
+ char *internalmic_name;
+#endif /* HAVE_ALSA */
+
+ GvcMixerControlState state;
+};
+
+enum {
+ STATE_CHANGED,
+ STREAM_ADDED,
+ STREAM_REMOVED,
+ STREAM_CHANGED,
+ CARD_ADDED,
+ CARD_REMOVED,
+ DEFAULT_SINK_CHANGED,
+ DEFAULT_SOURCE_CHANGED,
+ ACTIVE_OUTPUT_UPDATE,
+ ACTIVE_INPUT_UPDATE,
+ OUTPUT_ADDED,
+ INPUT_ADDED,
+ OUTPUT_REMOVED,
+ INPUT_REMOVED,
+ AUDIO_DEVICE_SELECTION_NEEDED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void gvc_mixer_control_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (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;
+}
+
+/**
+ * gvc_mixer_control_get_event_sink_input:
+ * @control:
+ *
+ * Returns: (transfer none):
+ */
+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;
+}
+
+static void
+gvc_mixer_control_stream_restore_cb (pa_context *c,
+ GvcMixerStream *new_stream,
+ const pa_ext_stream_restore_info *info,
+ GvcMixerControl *control)
+{
+ pa_operation *o;
+ pa_ext_stream_restore_info new_info;
+
+ if (new_stream == NULL)
+ return;
+
+ new_info.name = info->name;
+ new_info.channel_map = info->channel_map;
+ new_info.volume = info->volume;
+ new_info.mute = info->mute;
+
+ new_info.device = gvc_mixer_stream_get_name (new_stream);
+
+ o = pa_ext_stream_restore_write (control->priv->pa_context,
+ PA_UPDATE_REPLACE,
+ &new_info, 1,
+ TRUE, NULL, NULL);
+
+ if (o == NULL) {
+ g_warning ("pa_ext_stream_restore_write() failed: %s",
+ pa_strerror (pa_context_errno (control->priv->pa_context)));
+ return;
+ }
+
+ g_debug ("Changed default device for %s to %s", info->name, new_info.device);
+
+ pa_operation_unref (o);
+}
+
+static void
+gvc_mixer_control_stream_restore_sink_cb (pa_context *c,
+ const pa_ext_stream_restore_info *info,
+ int eol,
+ void *userdata)
+{
+ GvcMixerControl *control = (GvcMixerControl *) userdata;
+ if (eol || info == NULL || !g_str_has_prefix(info->name, "sink-input-by"))
+ return;
+ gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_sink_stream, info, control);
+}
+
+static void
+gvc_mixer_control_stream_restore_source_cb (pa_context *c,
+ const pa_ext_stream_restore_info *info,
+ int eol,
+ void *userdata)
+{
+ GvcMixerControl *control = (GvcMixerControl *) userdata;
+ if (eol || info == NULL || !g_str_has_prefix(info->name, "source-output-by"))
+ return;
+ gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_source_stream, info, control);
+}
+
+/**
+ * gvc_mixer_control_lookup_device_from_stream:
+ * @control:
+ * @stream:
+ *
+ * Returns: (transfer none): a #GvcUIDevice or %NULL
+ */
+GvcMixerUIDevice *
+gvc_mixer_control_lookup_device_from_stream (GvcMixerControl *control,
+ GvcMixerStream *stream)
+{
+ GList *devices, *d;
+ gboolean is_network_stream;
+ const GList *ports;
+ GvcMixerUIDevice *ret;
+
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+
+ if (GVC_IS_MIXER_SOURCE (stream))
+ devices = g_hash_table_get_values (control->priv->ui_inputs);
+ else
+ devices = g_hash_table_get_values (control->priv->ui_outputs);
+
+ ret = NULL;
+ ports = gvc_mixer_stream_get_ports (stream);
+ is_network_stream = (ports == NULL);
+
+ for (d = devices; d != NULL; d = d->next) {
+ GvcMixerUIDevice *device = d->data;
+ guint stream_id = G_MAXUINT;
+
+ g_object_get (G_OBJECT (device),
+ "stream-id", &stream_id,
+ NULL);
+
+ if (is_network_stream &&
+ stream_id == gvc_mixer_stream_get_id (stream)) {
+ g_debug ("lookup device from stream - %s - it is a network_stream ",
+ gvc_mixer_ui_device_get_description (device));
+ ret = device;
+ break;
+ } else if (!is_network_stream) {
+ const GvcMixerStreamPort *port;
+ port = gvc_mixer_stream_get_port (stream);
+
+ if (stream_id == gvc_mixer_stream_get_id (stream) &&
+ g_strcmp0 (gvc_mixer_ui_device_get_port (device),
+ port->port) == 0) {
+ g_debug ("lookup-device-from-stream found device: device description '%s', device port = '%s', device stream id %i AND stream port = '%s' stream id '%u' and stream description '%s'",
+ gvc_mixer_ui_device_get_description (device),
+ gvc_mixer_ui_device_get_port (device),
+ stream_id,
+ port->port,
+ gvc_mixer_stream_get_id (stream),
+ gvc_mixer_stream_get_description (stream));
+ ret = device;
+ break;
+ }
+ }
+ }
+
+ g_debug ("gvc_mixer_control_lookup_device_from_stream - Could not find a device for stream '%s'",gvc_mixer_stream_get_description (stream));
+
+ g_list_free (devices);
+
+ return ret;
+}
+
+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);
+
+ g_debug ("about to set default sink on server");
+ 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: %s",
+ pa_strerror (pa_context_errno (control->priv->pa_context)));
+ return FALSE;
+ }
+
+ pa_operation_unref (o);
+
+ control->priv->new_default_sink_stream = stream;
+ g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_sink_stream);
+
+ o = pa_ext_stream_restore_read (control->priv->pa_context,
+ gvc_mixer_control_stream_restore_sink_cb,
+ control);
+
+ if (o == NULL) {
+ g_warning ("pa_ext_stream_restore_read() failed: %s",
+ pa_strerror (pa_context_errno (control->priv->pa_context)));
+ return FALSE;
+ }
+
+ pa_operation_unref (o);
+
+ return TRUE;
+}
+
+gboolean
+gvc_mixer_control_set_default_source (GvcMixerControl *control,
+ GvcMixerStream *stream)
+{
+ GvcMixerUIDevice* input;
+ 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);
+
+ control->priv->new_default_source_stream = stream;
+ g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_source_stream);
+
+ o = pa_ext_stream_restore_read (control->priv->pa_context,
+ gvc_mixer_control_stream_restore_source_cb,
+ control);
+
+ if (o == NULL) {
+ g_warning ("pa_ext_stream_restore_read() failed: %s",
+ pa_strerror (pa_context_errno (control->priv->pa_context)));
+ return FALSE;
+ }
+
+ pa_operation_unref (o);
+
+ /* source change successful, update the UI. */
+ input = gvc_mixer_control_lookup_device_from_stream (control, stream);
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_INPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (input));
+
+ return TRUE;
+}
+
+/**
+ * gvc_mixer_control_get_default_sink:
+ * @control:
+ *
+ * Returns: (transfer none):
+ */
+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;
+}
+
+/**
+ * gvc_mixer_control_get_default_source:
+ * @control:
+ *
+ * Returns: (transfer none):
+ */
+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;
+}
+
+static gpointer
+gvc_mixer_control_lookup_id (GHashTable *hash_table,
+ guint id)
+{
+ return g_hash_table_lookup (hash_table,
+ GUINT_TO_POINTER (id));
+}
+
+/**
+ * gvc_mixer_control_lookup_stream_id:
+ * @control:
+ * @id:
+ *
+ * Returns: (transfer none):
+ */
+GvcMixerStream *
+gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
+ guint id)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+ return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
+}
+
+/**
+ * gvc_mixer_control_lookup_card_id:
+ * @control:
+ * @id:
+ *
+ * Returns: (transfer none):
+ */
+GvcMixerCard *
+gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
+ guint id)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+ return gvc_mixer_control_lookup_id (control->priv->cards, id);
+}
+
+/**
+ * gvc_mixer_control_lookup_output_id:
+ * @control:
+ * @id:
+ *
+ * Returns: (transfer none):
+ */
+GvcMixerUIDevice *
+gvc_mixer_control_lookup_output_id (GvcMixerControl *control,
+ guint id)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+ return gvc_mixer_control_lookup_id (control->priv->ui_outputs, id);
+}
+
+/**
+ * gvc_mixer_control_lookup_input_id:
+ * @control:
+ * @id:
+ *
+ * Returns: (transfer none):
+ */
+GvcMixerUIDevice *
+gvc_mixer_control_lookup_input_id (GvcMixerControl *control,
+ guint id)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+ return gvc_mixer_control_lookup_id (control->priv->ui_inputs, id);
+}
+
+/**
+ * gvc_mixer_control_get_stream_from_device:
+ * @control:
+ * @device:
+ *
+ * Returns: (transfer none):
+ */
+GvcMixerStream *
+gvc_mixer_control_get_stream_from_device (GvcMixerControl *control,
+ GvcMixerUIDevice *device)
+{
+ gint stream_id;
+
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ stream_id = gvc_mixer_ui_device_get_stream_id (device);
+
+ if (stream_id == GVC_MIXER_UI_DEVICE_INVALID) {
+ g_debug ("gvc_mixer_control_get_stream_from_device - device has a null stream");
+ return NULL;
+ }
+ return gvc_mixer_control_lookup_stream_id (control, stream_id);
+}
+
+/**
+ * gvc_mixer_control_change_profile_on_selected_device:
+ * @control:
+ * @device:
+ * @profile: (allow-none): Can be %NULL if any profile present on this port is okay
+ *
+ * Returns: This method will attempt to swap the profile on the card of
+ * the device with given profile name. If successfull it will set the
+ * preferred profile on that device so as we know the next time the user
+ * moves to that device it should have this profile active.
+ */
+gboolean
+gvc_mixer_control_change_profile_on_selected_device (GvcMixerControl *control,
+ GvcMixerUIDevice *device,
+ const gchar *profile)
+{
+ const gchar *best_profile;
+ GvcMixerCardProfile *current_profile;
+ GvcMixerCard *card;
+
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), FALSE);
+
+ g_object_get (G_OBJECT (device), "card", &card, NULL);
+ current_profile = gvc_mixer_card_get_profile (card);
+
+ if (current_profile)
+ best_profile = gvc_mixer_ui_device_get_best_profile (device, profile, current_profile->profile);
+ else
+ best_profile = profile;
+
+ g_assert (best_profile);
+
+ g_debug ("Selected '%s', moving to profile '%s' on card '%s' on stream id %i",
+ profile ? profile : "(any)", best_profile,
+ gvc_mixer_card_get_name (card),
+ gvc_mixer_ui_device_get_stream_id (device));
+
+ g_debug ("default sink name = %s and default sink id %u",
+ control->priv->default_sink_name,
+ control->priv->default_sink_id);
+
+ control->priv->profile_swapping_device_id = gvc_mixer_ui_device_get_id (device);
+
+ if (gvc_mixer_card_change_profile (card, best_profile)) {
+ gvc_mixer_ui_device_set_user_preferred_profile (device, best_profile);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * gvc_mixer_control_change_output:
+ * @control:
+ * @output:
+ * This method is called from the UI when the user selects a previously unselected device.
+ * - Firstly it queries the stream from the device.
+ * - It assumes that if the stream is null that it cannot be a bluetooth or network stream (they never show unless they have valid sinks and sources)
+ * In the scenario of a NULL stream on the device
+ * - It fetches the device's preferred profile or if NUll the profile with the highest priority on that device.
+ * - It then caches this device in control->priv->cached_desired_output_id so that when the update_sink triggered
+ * from when we attempt to change profile we will know exactly what device to highlight on that stream.
+ * - It attempts to swap the profile on the card from that device and returns.
+ * - Next, it handles network or bluetooth streams that only require their stream to be made the default.
+ * - Next it deals with port changes so if the stream's active port is not the same as the port on the device
+ * it will attempt to change the port on that stream to be same as the device. If this fails it will return.
+ * - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active output device.
+ */
+void
+gvc_mixer_control_change_output (GvcMixerControl *control,
+ GvcMixerUIDevice* output)
+{
+ GvcMixerStream *stream;
+ GvcMixerStream *default_stream;
+ const GvcMixerStreamPort *active_port;
+ const gchar *output_port;
+
+ g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
+ g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (output));
+
+ g_debug ("control change output");
+
+ stream = gvc_mixer_control_get_stream_from_device (control, output);
+ if (stream == NULL) {
+ gvc_mixer_control_change_profile_on_selected_device (control,
+ output, NULL);
+ return;
+ }
+
+ /* Handle a network sink as a portless or cardless device */
+ if (!gvc_mixer_ui_device_has_ports (output)) {
+ g_debug ("Did we try to move to a software/bluetooth sink ?");
+ if (gvc_mixer_control_set_default_sink (control, stream)) {
+ /* sink change was successful, update the UI.*/
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_OUTPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (output));
+ }
+ else {
+ g_warning ("Failed to set default sink with stream from output %s",
+ gvc_mixer_ui_device_get_description (output));
+ }
+ return;
+ }
+
+ active_port = gvc_mixer_stream_get_port (stream);
+ output_port = gvc_mixer_ui_device_get_port (output);
+ /* First ensure the correct port is active on the sink */
+ if (g_strcmp0 (active_port->port, output_port) != 0) {
+ g_debug ("Port change, switch to = %s", output_port);
+ if (gvc_mixer_stream_change_port (stream, output_port) == FALSE) {
+ g_warning ("Could not change port !");
+ return;
+ }
+ }
+
+ default_stream = gvc_mixer_control_get_default_sink (control);
+
+ /* Finally if we are not on the correct stream, swap over. */
+ if (stream != default_stream) {
+ GvcMixerUIDevice* device;
+
+ g_debug ("Attempting to swap over to stream %s ",
+ gvc_mixer_stream_get_description (stream));
+ if (gvc_mixer_control_set_default_sink (control, stream)) {
+ device = gvc_mixer_control_lookup_device_from_stream (control, stream);
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_OUTPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+ } else {
+ /* If the move failed for some reason reset the UI. */
+ device = gvc_mixer_control_lookup_device_from_stream (control, default_stream);
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_OUTPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+ }
+ }
+}
+
+
+/**
+ * gvc_mixer_control_change_input:
+ * @control:
+ * @input:
+ * This method is called from the UI when the user selects a previously unselected device.
+ * - Firstly it queries the stream from the device.
+ * - It assumes that if the stream is null that it cannot be a bluetooth or network stream (they never show unless they have valid sinks and sources)
+ * In the scenario of a NULL stream on the device
+ * - It fetches the device's preferred profile or if NUll the profile with the highest priority on that device.
+ * - It then caches this device in control->priv->cached_desired_input_id so that when the update_source triggered
+ * from when we attempt to change profile we will know exactly what device to highlight on that stream.
+ * - It attempts to swap the profile on the card from that device and returns.
+ * - Next, it handles network or bluetooth streams that only require their stream to be made the default.
+ * - Next it deals with port changes so if the stream's active port is not the same as the port on the device
+ * it will attempt to change the port on that stream to be same as the device. If this fails it will return.
+ * - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active input device.
+ */
+void
+gvc_mixer_control_change_input (GvcMixerControl *control,
+ GvcMixerUIDevice* input)
+{
+ GvcMixerStream *stream;
+ GvcMixerStream *default_stream;
+ const GvcMixerStreamPort *active_port;
+ const gchar *input_port;
+
+ g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
+ g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (input));
+
+ stream = gvc_mixer_control_get_stream_from_device (control, input);
+ if (stream == NULL) {
+ gvc_mixer_control_change_profile_on_selected_device (control,
+ input, NULL);
+ return;
+ }
+
+ /* Handle a network sink as a portless/cardless device */
+ if (!gvc_mixer_ui_device_has_ports (input)) {
+ g_debug ("Did we try to move to a software/bluetooth source ?");
+ if (! gvc_mixer_control_set_default_source (control, stream)) {
+ g_warning ("Failed to set default source with stream from input %s",
+ gvc_mixer_ui_device_get_description (input));
+ }
+ return;
+ }
+
+ active_port = gvc_mixer_stream_get_port (stream);
+ input_port = gvc_mixer_ui_device_get_port (input);
+ /* First ensure the correct port is active on the sink */
+ if (g_strcmp0 (active_port->port, input_port) != 0) {
+ g_debug ("Port change, switch to = %s", input_port);
+ if (gvc_mixer_stream_change_port (stream, input_port) == FALSE) {
+ g_warning ("Could not change port!");
+ return;
+ }
+ }
+
+ default_stream = gvc_mixer_control_get_default_source (control);
+
+ /* Finally if we are not on the correct stream, swap over. */
+ if (stream != default_stream) {
+ g_debug ("change-input - attempting to swap over to stream %s",
+ gvc_mixer_stream_get_description (stream));
+ gvc_mixer_control_set_default_source (control, 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_name_collate (const char *namea,
+ const char *nameb)
+{
+ if (nameb == NULL && namea == NULL)
+ return 0;
+ if (nameb == NULL)
+ return 1;
+ if (namea == NULL)
+ return -1;
+
+ return g_utf8_collate (namea, nameb);
+}
+
+static int
+gvc_card_collate (GvcMixerCard *a,
+ GvcMixerCard *b)
+{
+ const char *namea;
+ const char *nameb;
+
+ g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0);
+ g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0);
+
+ namea = gvc_mixer_card_get_name (a);
+ nameb = gvc_mixer_card_get_name (b);
+
+ return gvc_name_collate (namea, nameb);
+}
+
+/**
+ * gvc_mixer_control_get_cards:
+ * @control:
+ *
+ * Returns: (transfer container) (element-type Gvc.MixerCard):
+ */
+GSList *
+gvc_mixer_control_get_cards (GvcMixerControl *control)
+{
+ GSList *retval;
+
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
+
+ retval = NULL;
+ g_hash_table_foreach (control->priv->cards,
+ listify_hash_values_hfunc,
+ &retval);
+ return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
+}
+
+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 gvc_name_collate (namea, nameb);
+}
+
+/**
+ * gvc_mixer_control_get_streams:
+ * @control:
+ *
+ * Returns: (transfer container) (element-type Gvc.MixerStream):
+ */
+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);
+}
+
+/**
+ * gvc_mixer_control_get_sinks:
+ * @control:
+ *
+ * Returns: (transfer container) (element-type Gvc.MixerSink):
+ */
+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);
+}
+
+/**
+ * gvc_mixer_control_get_sources:
+ * @control:
+ *
+ * Returns: (transfer container) (element-type Gvc.MixerSource):
+ */
+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);
+}
+
+/**
+ * gvc_mixer_control_get_sink_inputs:
+ * @control:
+ *
+ * Returns: (transfer container) (element-type Gvc.MixerSinkInput):
+ */
+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);
+}
+
+/**
+ * gvc_mixer_control_get_source_outputs:
+ * @control:
+ *
+ * Returns: (transfer container) (element-type Gvc.MixerSourceOutput):
+ */
+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) {
+ control->priv->state = GVC_STATE_READY;
+ g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_READY);
+ }
+}
+
+GvcMixerControlState
+gvc_mixer_control_get_state (GvcMixerControl *control)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), GVC_STATE_CLOSED);
+
+ return control->priv->state;
+}
+
+static void
+on_default_source_port_notify (GObject *object,
+ GParamSpec *pspec,
+ GvcMixerControl *control)
+{
+ char *port;
+ GvcMixerUIDevice *input;
+
+ g_object_get (object, "port", &port, NULL);
+ input = gvc_mixer_control_lookup_device_from_stream (control,
+ GVC_MIXER_STREAM (object));
+
+ g_debug ("on_default_source_port_notify - moved to port '%s' which SHOULD ?? correspond to output '%s'",
+ port,
+ gvc_mixer_ui_device_get_description (input));
+
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_INPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (input));
+
+ g_free (port);
+}
+
+
+static void
+_set_default_source (GvcMixerControl *control,
+ GvcMixerStream *stream)
+{
+ guint new_id;
+
+ if (stream == NULL) {
+ control->priv->default_source_id = 0;
+ control->priv->default_source_is_set = FALSE;
+ g_signal_emit (control,
+ signals[DEFAULT_SOURCE_CHANGED],
+ 0,
+ PA_INVALID_INDEX);
+ return;
+ }
+
+ new_id = gvc_mixer_stream_get_id (stream);
+
+ if (control->priv->default_source_id != new_id) {
+ GvcMixerUIDevice *input;
+ 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);
+
+ if (control->priv->default_source_is_set) {
+ g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control),
+ on_default_source_port_notify,
+ control);
+ }
+
+ g_signal_connect (stream,
+ "notify::port",
+ G_CALLBACK (on_default_source_port_notify),
+ control);
+
+ input = gvc_mixer_control_lookup_device_from_stream (control, stream);
+
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_INPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (input));
+ }
+}
+
+static void
+on_default_sink_port_notify (GObject *object,
+ GParamSpec *pspec,
+ GvcMixerControl *control)
+{
+ char *port;
+ GvcMixerUIDevice *output;
+
+ g_object_get (object, "port", &port, NULL);
+
+ output = gvc_mixer_control_lookup_device_from_stream (control,
+ GVC_MIXER_STREAM (object));
+ if (output != NULL) {
+ g_debug ("on_default_sink_port_notify - moved to port %s - which SHOULD correspond to output %s",
+ port,
+ gvc_mixer_ui_device_get_description (output));
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_OUTPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (output));
+ }
+ g_free (port);
+}
+
+static void
+_set_default_sink (GvcMixerControl *control,
+ GvcMixerStream *stream)
+{
+ guint new_id;
+
+ if (stream == NULL) {
+ /* Don't tell front-ends about an unset default
+ * sink if it's already unset */
+ if (control->priv->default_sink_is_set == FALSE)
+ return;
+ control->priv->default_sink_id = 0;
+ control->priv->default_sink_is_set = FALSE;
+ g_signal_emit (control,
+ signals[DEFAULT_SINK_CHANGED],
+ 0,
+ PA_INVALID_INDEX);
+ return;
+ }
+
+ new_id = gvc_mixer_stream_get_id (stream);
+
+ if (control->priv->default_sink_id != new_id) {
+ GvcMixerUIDevice *output;
+ if (control->priv->default_sink_is_set) {
+ g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control),
+ on_default_sink_port_notify,
+ control);
+ }
+
+ 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);
+
+ g_signal_connect (stream,
+ "notify::port",
+ G_CALLBACK (on_default_sink_port_notify),
+ control);
+
+ output = gvc_mixer_control_lookup_device_from_stream (control, stream);
+
+ g_debug ("active_sink change");
+
+ g_signal_emit (G_OBJECT (control),
+ signals[ACTIVE_OUTPUT_UPDATE],
+ 0,
+ gvc_mixer_ui_device_get_id (output));
+ }
+}
+
+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 = FALSE;
+
+ if ((control->priv->default_source_name == NULL
+ && name != NULL)
+ || (control->priv->default_source_name != NULL
+ && 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 = FALSE;
+
+ if ((control->priv->default_sink_name == NULL
+ && name != NULL)
+ || (control->priv->default_sink_name != NULL
+ && 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) {
+ g_debug ("update server");
+ 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));
+}
+
+/* This method will match individual stream ports against its corresponding device
+ * It does this by:
+ * - iterates through our devices and finds the one where the card-id on the device is the same as the card-id on the stream
+ * and the port-name on the device is the same as the streamport-name.
+ * This should always find a match and is used exclusively by sync_devices().
+ */
+static gboolean
+match_stream_with_devices (GvcMixerControl *control,
+ GvcMixerStreamPort *stream_port,
+ GvcMixerStream *stream)
+{
+ GList *devices, *d;
+ guint stream_card_id;
+ guint stream_id;
+ gboolean in_possession = FALSE;
+
+ stream_id = gvc_mixer_stream_get_id (stream);
+ stream_card_id = gvc_mixer_stream_get_card_index (stream);
+
+ devices = g_hash_table_get_values (GVC_IS_MIXER_SOURCE (stream) ? control->priv->ui_inputs : control->priv->ui_outputs);
+
+ for (d = devices; d != NULL; d = d->next) {
+ GvcMixerUIDevice *device;
+ gint device_stream_id;
+ gchar *device_port_name;
+ gchar *origin;
+ gchar *description;
+ GvcMixerCard *card;
+ guint card_id;
+
+ device = d->data;
+ g_object_get (G_OBJECT (device),
+ "stream-id", &device_stream_id,
+ "card", &card,
+ "origin", &origin,
+ "description", &description,
+ "port-name", &device_port_name,
+ NULL);
+
+ if (card == NULL) {
+ if (device_stream_id == stream_id) {
+ g_debug ("Matched stream %u with card-less device '%s', with stream already setup",
+ stream_id, description);
+ in_possession = TRUE;
+ }
+ } else {
+ card_id = gvc_mixer_card_get_index (card);
+
+ g_debug ("Attempt to match_stream update_with_existing_outputs - Try description : '%s', origin : '%s', device port name : '%s', card : %p, AGAINST stream port: '%s', sink card id %i",
+ description,
+ origin,
+ device_port_name,
+ card,
+ stream_port->port,
+ stream_card_id);
+
+ if (stream_card_id == card_id &&
+ g_strcmp0 (device_port_name, stream_port->port) == 0) {
+ g_debug ("Match device with stream: We have a match with description: '%s', origin: '%s', cached already with device id %u, so set stream id to %i",
+ description,
+ origin,
+ gvc_mixer_ui_device_get_id (device),
+ stream_id);
+
+ g_object_set (G_OBJECT (device),
+ "stream-id", (gint)stream_id,
+ NULL);
+ in_possession = TRUE;
+ }
+ }
+
+ g_free (device_port_name);
+ g_free (origin);
+ g_free (description);
+
+ if (in_possession == TRUE)
+ break;
+ }
+
+ g_list_free (devices);
+ return in_possession;
+}
+
+/*
+ * This method attempts to match a sink or source with its relevant UI device.
+ * GvcMixerStream can represent both a sink or source.
+ * Using static card port introspection implies that we know beforehand what
+ * outputs and inputs are available to the user.
+ * But that does not mean that all of these inputs and outputs are available to be used.
+ * For instance we might be able to see that there is a HDMI port available but if
+ * we are on the default analog stereo output profile there is no valid sink for
+ * that HDMI device. We first need to change profile and when update_sink() is called
+ * only then can we match the new hdmi sink with its corresponding device.
+ *
+ * Firstly it checks to see if the incoming stream has no ports.
+ * - If a stream has no ports but has a valid card ID (bluetooth), it will attempt
+ * to match the device with the stream using the card id.
+ * - If a stream has no ports and no valid card id, it goes ahead and makes a new
+ * device (software/network devices are only detectable at the sink/source level)
+ * If the stream has ports it will match each port against the stream using match_stream_with_devices().
+ *
+ * This method should always find a match.
+ */
+static void
+sync_devices (GvcMixerControl *control,
+ GvcMixerStream* stream)
+{
+ /* Go through ports to see what outputs can be created. */
+ const GList *stream_ports;
+ const GList *n = NULL;
+ gboolean is_output = !GVC_IS_MIXER_SOURCE (stream);
+ gint stream_port_count = 0;
+
+ stream_ports = gvc_mixer_stream_get_ports (stream);
+
+ if (stream_ports == NULL) {
+ GvcMixerUIDevice *device;
+ /* Bluetooth, no ports but a valid card */
+ if (gvc_mixer_stream_get_card_index (stream) != PA_INVALID_INDEX) {
+ GList *devices, *d;
+ gboolean in_possession = FALSE;
+
+ devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
+
+ for (d = devices; d != NULL; d = d->next) {
+ GvcMixerCard *card;
+ guint card_id;
+
+ device = d->data;
+
+ g_object_get (G_OBJECT (device),
+ "card", &card,
+ NULL);
+ card_id = gvc_mixer_card_get_index (card);
+ g_debug ("sync devices, device description - '%s', device card id - %i, stream description - %s, stream card id - %i",
+ gvc_mixer_ui_device_get_description (device),
+ card_id,
+ gvc_mixer_stream_get_description (stream),
+ gvc_mixer_stream_get_card_index (stream));
+ if (card_id == gvc_mixer_stream_get_card_index (stream)) {
+ in_possession = TRUE;
+ break;
+ }
+ }
+ g_list_free (devices);
+
+ if (!in_possession) {
+ g_warning ("Couldn't match the portless stream (with card) - '%s' is it an input ? -> %i, streams card id -> %i",
+ gvc_mixer_stream_get_description (stream),
+ GVC_IS_MIXER_SOURCE (stream),
+ gvc_mixer_stream_get_card_index (stream));
+ return;
+ }
+
+ g_object_set (G_OBJECT (device),
+ "stream-id", (gint)gvc_mixer_stream_get_id (stream),
+ "description", gvc_mixer_stream_get_description (stream),
+ "origin", "", /*Leave it empty for these special cases*/
+ "port-name", NULL,
+ "port-available", TRUE,
+ NULL);
+ } else { /* Network sink/source has no ports and no card. */
+ GObject *object;
+
+ object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
+ "stream-id", (gint)gvc_mixer_stream_get_id (stream),
+ "description", gvc_mixer_stream_get_description (stream),
+ "origin", "", /* Leave it empty for these special cases */
+ "port-name", NULL,
+ "port-available", TRUE,
+ NULL);
+ device = GVC_MIXER_UI_DEVICE (object);
+
+ g_hash_table_insert (is_output ? control->priv->ui_outputs : control->priv->ui_inputs,
+ GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)),
+ g_object_ref (device));
+
+ }
+ g_signal_emit (G_OBJECT (control),
+ signals[is_output ? OUTPUT_ADDED : INPUT_ADDED],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+
+ return;
+ }
+
+ /* Go ahead and make sure to match each port against a previously created device */
+ for (n = stream_ports; n != NULL; n = n->next) {
+
+ GvcMixerStreamPort *stream_port;
+ stream_port = n->data;
+ stream_port_count ++;
+
+ if (match_stream_with_devices (control, stream_port, stream))
+ continue;
+
+ g_warning ("Sync_devices: Failed to match stream id: %u, description: '%s', origin: '%s'",
+ gvc_mixer_stream_get_id (stream),
+ stream_port->human_port,
+ gvc_mixer_stream_get_description (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_DEVICE_ICON_NAME))) {
+ goto finish;
+ }
+
+ 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 GvcMixerStreamState
+translate_pa_state (pa_sink_state_t state) {
+ switch (state) {
+ case PA_SINK_RUNNING:
+ return GVC_STREAM_STATE_RUNNING;
+ case PA_SINK_IDLE:
+ return GVC_STREAM_STATE_IDLE;
+ case PA_SINK_SUSPENDED:
+ return GVC_STREAM_STATE_SUSPENDED;
+ case PA_SINK_INIT:
+ case PA_SINK_INVALID_STATE:
+ case PA_SINK_UNLINKED:
+ default:
+ return GVC_STREAM_STATE_INVALID;
+ }
+}
+
+/*
+ * Called when anything changes with a sink.
+ */
+static void
+update_sink (GvcMixerControl *control,
+ const pa_sink_info *info)
+{
+ GvcMixerStream *stream;
+ gboolean is_new;
+ pa_volume_t max_volume;
+ GvcChannelMap *map;
+ 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
+
+ map = NULL;
+ is_new = FALSE;
+ stream = g_hash_table_lookup (control->priv->sinks,
+ GUINT_TO_POINTER (info->index));
+
+ if (stream == NULL) {
+ GList *list = NULL;
+ guint i;
+
+ map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
+ stream = gvc_mixer_sink_new (control->priv->pa_context,
+ info->index,
+ map);
+
+ for (i = 0; i < info->n_ports; i++) {
+ GvcMixerStreamPort *port;
+
+ port = g_slice_new0 (GvcMixerStreamPort);
+ port->port = g_strdup (info->ports[i]->name);
+ port->human_port = g_strdup (info->ports[i]->description);
+ port->priority = info->ports[i]->priority;
+ port->available = info->ports[i]->available != PA_PORT_AVAILABLE_NO;
+
+ list = g_list_prepend (list, port);
+ }
+ gvc_mixer_stream_set_ports (stream, list);
+
+ g_object_unref (map);
+ is_new = TRUE;
+
+ } else if (gvc_mixer_stream_is_running (stream)) {
+ /* Ignore events if volume changes are outstanding */
+ g_debug ("Ignoring event, volume changes are outstanding");
+ return;
+ }
+
+ max_volume = pa_cvolume_max (&info->volume);
+ gvc_mixer_stream_set_name (stream, info->name);
+ gvc_mixer_stream_set_card_index (stream, info->card);
+ gvc_mixer_stream_set_description (stream, info->description);
+ set_icon_name_from_proplist (stream, info->proplist, "audio-card");
+ gvc_mixer_stream_set_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
+ gvc_mixer_stream_set_sysfs_path (stream, pa_proplist_gets (info->proplist, "sysfs.path"));
+ 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));
+ gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
+ gvc_mixer_stream_set_state (stream, translate_pa_state (info->state));
+
+ /* Messy I know but to set the port everytime regardless of whether it has changed will cost us a
+ * port change notify signal which causes the frontend to resync.
+ * Only update the UI when something has changed. */
+ if (info->active_port != NULL) {
+ if (is_new)
+ gvc_mixer_stream_set_port (stream, info->active_port->name);
+ else {
+ const GvcMixerStreamPort *active_port;
+ active_port = gvc_mixer_stream_get_port (stream);
+ if (active_port == NULL ||
+ g_strcmp0 (active_port->port, info->active_port->name) != 0) {
+ g_debug ("update sink - apparently a port update");
+ gvc_mixer_stream_set_port (stream, info->active_port->name);
+ }
+ }
+ }
+
+ if (is_new) {
+ g_debug ("update sink - is new");
+
+ g_hash_table_insert (control->priv->sinks,
+ GUINT_TO_POINTER (info->index),
+ g_object_ref (stream));
+ add_stream (control, stream);
+ /* Always sink on a new stream to able to assign the right stream id
+ * to the appropriate outputs (multiple potential outputs per stream). */
+ sync_devices (control, stream);
+ } else {
+ g_signal_emit (G_OBJECT (control),
+ signals[STREAM_CHANGED],
+ 0,
+ gvc_mixer_stream_get_id (stream));
+ }
+
+ /*
+ * When we change profile on a device that is not the server default sink,
+ * it will jump back to the default sink set by the server to prevent the audio setup from being 'outputless'.
+ * All well and good but then when we get the new stream created for the new profile how do we know
+ * that this is the intended default or selected device the user wishes to use.
+ * This is messy but it's the only reliable way that it can be done without ripping the whole thing apart.
+ */
+ if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
+ GvcMixerUIDevice *dev = NULL;
+ dev = gvc_mixer_control_lookup_output_id (control, control->priv->profile_swapping_device_id);
+ if (dev != NULL) {
+ /* now check to make sure this new stream is the same stream just matched and set on the device object */
+ if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
+ g_debug ("Looks like we profile swapped on a non server default sink");
+ gvc_mixer_control_set_default_sink (control, stream);
+ control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
+ }
+ }
+ }
+
+ if (control->priv->default_sink_name != NULL
+ && info->name != NULL
+ && strcmp (control->priv->default_sink_name, info->name) == 0) {
+ _set_default_sink (control, stream);
+ }
+
+ if (map == NULL)
+ map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream);
+
+ gvc_channel_map_volume_changed (map, &info->volume, FALSE);
+}
+
+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
+
+ /* completely ignore monitors, they're not real sources */
+ if (info->monitor_of_sink != PA_INVALID_INDEX) {
+ return;
+ }
+
+ is_new = FALSE;
+
+ stream = g_hash_table_lookup (control->priv->sources,
+ GUINT_TO_POINTER (info->index));
+ if (stream == NULL) {
+ GList *list = NULL;
+ guint i;
+ 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);
+
+ for (i = 0; i < info->n_ports; i++) {
+ GvcMixerStreamPort *port;
+
+ port = g_slice_new0 (GvcMixerStreamPort);
+ port->port = g_strdup (info->ports[i]->name);
+ port->human_port = g_strdup (info->ports[i]->description);
+ port->priority = info->ports[i]->priority;
+ list = g_list_prepend (list, port);
+ }
+ gvc_mixer_stream_set_ports (stream, list);
+
+ g_object_unref (map);
+ is_new = TRUE;
+ } else if (gvc_mixer_stream_is_running (stream)) {
+ /* Ignore events if volume changes are outstanding */
+ g_debug ("Ignoring event, volume changes are outstanding");
+ return;
+ }
+
+ max_volume = pa_cvolume_max (&info->volume);
+
+ gvc_mixer_stream_set_name (stream, info->name);
+ gvc_mixer_stream_set_card_index (stream, info->card);
+ gvc_mixer_stream_set_description (stream, info->description);
+ set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
+ gvc_mixer_stream_set_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
+ 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));
+ gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
+ g_debug ("update source");
+
+ if (info->active_port != NULL) {
+ if (is_new)
+ gvc_mixer_stream_set_port (stream, info->active_port->name);
+ else {
+ const GvcMixerStreamPort *active_port;
+ active_port = gvc_mixer_stream_get_port (stream);
+ if (active_port == NULL ||
+ g_strcmp0 (active_port->port, info->active_port->name) != 0) {
+ g_debug ("update source - apparently a port update");
+ gvc_mixer_stream_set_port (stream, info->active_port->name);
+ }
+ }
+ }
+
+ if (is_new) {
+ g_hash_table_insert (control->priv->sources,
+ GUINT_TO_POINTER (info->index),
+ g_object_ref (stream));
+ add_stream (control, stream);
+ sync_devices (control, stream);
+ } else {
+ g_signal_emit (G_OBJECT (control),
+ signals[STREAM_CHANGED],
+ 0,
+ gvc_mixer_stream_get_id (stream));
+ }
+
+ if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
+ GvcMixerUIDevice *dev = NULL;
+
+ dev = gvc_mixer_control_lookup_input_id (control, control->priv->profile_swapping_device_id);
+
+ if (dev != NULL) {
+ /* now check to make sure this new stream is the same stream just matched and set on the device object */
+ if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
+ g_debug ("Looks like we profile swapped on a non server default source");
+ gvc_mixer_control_set_default_source (control, stream);
+ control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
+ }
+ }
+ }
+ 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_is_event_stream_from_proplist (GvcMixerStream *stream,
+ pa_proplist *l)
+{
+ const char *t;
+ gboolean is_event_stream;
+
+ is_event_stream = FALSE;
+
+ if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
+ if (g_str_equal (t, "event"))
+ is_event_stream = TRUE;
+ }
+
+ gvc_mixer_stream_set_is_event_stream (stream, is_event_stream);
+}
+
+static void
+set_application_id_from_proplist (GvcMixerStream *stream,
+ pa_proplist *l)
+{
+ const char *t;
+
+ if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) {
+ gvc_mixer_stream_set_application_id (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;
+ } else if (gvc_mixer_stream_is_running (stream)) {
+ /* Ignore events if volume changes are outstanding */
+ g_debug ("Ignoring event, volume changes are outstanding");
+ return;
+ }
+
+ 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_application_id_from_proplist (stream, info->proplist);
+ set_is_event_stream_from_proplist (stream, info->proplist);
+ 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);
+ gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX);
+
+ if (is_new) {
+ g_hash_table_insert (control->priv->sink_inputs,
+ GUINT_TO_POINTER (info->index),
+ g_object_ref (stream));
+ add_stream (control, stream);
+ } else {
+ g_signal_emit (G_OBJECT (control),
+ signals[STREAM_CHANGED],
+ 0,
+ gvc_mixer_stream_get_id (stream));
+ }
+}
+
+static void
+update_source_output (GvcMixerControl *control,
+ const pa_source_output_info *info)
+{
+ GvcMixerStream *stream;
+ gboolean is_new;
+ pa_volume_t max_volume;
+ 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));
+
+ max_volume = pa_cvolume_max (&info->volume);
+
+ gvc_mixer_stream_set_name (stream, name);
+ gvc_mixer_stream_set_description (stream, info->name);
+ set_application_id_from_proplist (stream, info->proplist);
+ set_is_event_stream_from_proplist (stream, info->proplist);
+ gvc_mixer_stream_set_volume (stream, (guint)max_volume);
+ gvc_mixer_stream_set_is_muted (stream, info->mute);
+ set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
+
+ if (is_new) {
+ g_hash_table_insert (control->priv->source_outputs,
+ GUINT_TO_POINTER (info->index),
+ g_object_ref (stream));
+ add_stream (control, stream);
+ } else {
+ g_signal_emit (G_OBJECT (control),
+ signals[STREAM_CHANGED],
+ 0,
+ gvc_mixer_stream_get_id (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 char *
+card_num_streams_to_status (guint sinks,
+ guint sources)
+{
+ char *sinks_str;
+ char *sources_str;
+ char *ret;
+
+ if (sinks == 0 && sources == 0) {
+ /* translators:
+ * The device has been disabled */
+ return g_strdup (_("Disabled"));
+ }
+ if (sinks == 0) {
+ sinks_str = NULL;
+ } else {
+ /* translators:
+ * The number of sound outputs on a particular device */
+ sinks_str = g_strdup_printf (ngettext ("%u Output",
+ "%u Outputs",
+ sinks),
+ sinks);
+ }
+ if (sources == 0) {
+ sources_str = NULL;
+ } else {
+ /* translators:
+ * The number of sound inputs on a particular device */
+ sources_str = g_strdup_printf (ngettext ("%u Input",
+ "%u Inputs",
+ sources),
+ sources);
+ }
+ if (sources_str == NULL)
+ return sinks_str;
+ if (sinks_str == NULL)
+ return sources_str;
+ ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
+ g_free (sinks_str);
+ g_free (sources_str);
+ return ret;
+}
+
+/*
+ * A utility method to gather which card profiles are relevant to the port .
+ */
+static GList *
+determine_profiles_for_port (pa_card_port_info *port,
+ GList* card_profiles)
+{
+ guint i;
+ GList *supported_profiles = NULL;
+ GList *p;
+ for (i = 0; i < port->n_profiles; i++) {
+ for (p = card_profiles; p != NULL; p = p->next) {
+ GvcMixerCardProfile *prof;
+ prof = p->data;
+ if (g_strcmp0 (port->profiles[i]->name, prof->profile) == 0)
+ supported_profiles = g_list_append (supported_profiles, prof);
+ }
+ }
+ g_debug ("%i profiles supported on port %s",
+ g_list_length (supported_profiles),
+ port->description);
+ return g_list_sort (supported_profiles, (GCompareFunc) gvc_mixer_card_profile_compare);
+}
+
+static gboolean
+is_card_port_an_output (GvcMixerCardPort* port)
+{
+ return port->direction == PA_DIRECTION_OUTPUT ? TRUE : FALSE;
+}
+
+/*
+ * This method will create a ui device for the given port.
+ */
+static void
+create_ui_device_from_port (GvcMixerControl* control,
+ GvcMixerCardPort* port,
+ GvcMixerCard* card)
+{
+ GvcMixerUIDeviceDirection direction;
+ GObject *object;
+ GvcMixerUIDevice *uidevice;
+ gboolean available = port->available != PA_PORT_AVAILABLE_NO;
+
+ direction = (is_card_port_an_output (port) == TRUE) ? UIDeviceOutput : UIDeviceInput;
+
+ object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
+ "type", (guint)direction,
+ "card", card,
+ "port-name", port->port,
+ "description", port->human_port,
+ "origin", gvc_mixer_card_get_name (card),
+ "port-available", available,
+ "icon-name", port->icon_name,
+ NULL);
+
+ uidevice = GVC_MIXER_UI_DEVICE (object);
+ gvc_mixer_ui_device_set_profiles (uidevice, port->profiles);
+
+ g_hash_table_insert (is_card_port_an_output (port) ? control->priv->ui_outputs : control->priv->ui_inputs,
+ GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (uidevice)),
+ uidevice);
+
+
+ if (available) {
+ g_signal_emit (G_OBJECT (control),
+ signals[is_card_port_an_output (port) ? OUTPUT_ADDED : INPUT_ADDED],
+ 0,
+ gvc_mixer_ui_device_get_id (uidevice));
+ }
+
+ g_debug ("create_ui_device_from_port, direction %u, description '%s', origin '%s', port available %i",
+ direction,
+ port->human_port,
+ gvc_mixer_card_get_name (card),
+ available);
+}
+
+/*
+ * This method will match up GvcMixerCardPorts with existing devices.
+ * A match is achieved if the device's card-id and the port's card-id are the same
+ * && the device's port-name and the card-port's port member are the same.
+ * A signal is then sent adding or removing that device from the UI depending on the availability of the port.
+ */
+static void
+match_card_port_with_existing_device (GvcMixerControl *control,
+ GvcMixerCardPort *card_port,
+ GvcMixerCard *card,
+ gboolean available)
+{
+ GList *d;
+ GList *devices;
+ GvcMixerUIDevice *device;
+ gboolean is_output = is_card_port_an_output (card_port);
+
+ devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
+
+ for (d = devices; d != NULL; d = d->next) {
+ GvcMixerCard *device_card;
+ gchar *device_port_name;
+
+ device = d->data;
+ g_object_get (G_OBJECT (device),
+ "card", &device_card,
+ "port-name", &device_port_name,
+ NULL);
+
+ if (g_strcmp0 (card_port->port, device_port_name) == 0 &&
+ device_card == card) {
+ g_debug ("Found the relevant device %s, update its port availability flag to %i, is_output %i",
+ device_port_name,
+ available,
+ is_output);
+ g_object_set (G_OBJECT (device),
+ "port-available", available, NULL);
+ g_signal_emit (G_OBJECT (control),
+ is_output ? signals[available ? OUTPUT_ADDED : OUTPUT_REMOVED] : signals[available ? INPUT_ADDED : INPUT_REMOVED],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+ }
+ g_free (device_port_name);
+ }
+
+ g_list_free (devices);
+}
+
+static void
+create_ui_device_from_card (GvcMixerControl *control,
+ GvcMixerCard *card)
+{
+ GObject *object;
+ GvcMixerUIDevice *in;
+ GvcMixerUIDevice *out;
+ const GList *profiles;
+
+ /* For now just create two devices and presume this device is multi directional
+ * Ensure to remove both on card removal (available to false by default) */
+ profiles = gvc_mixer_card_get_profiles (card);
+
+ g_debug ("Portless card just registered - %i", gvc_mixer_card_get_index (card));
+
+ object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
+ "type", UIDeviceInput,
+ "description", gvc_mixer_card_get_name (card),
+ "origin", "", /* Leave it empty for these special cases */
+ "port-name", NULL,
+ "port-available", FALSE,
+ "card", card,
+ NULL);
+ in = GVC_MIXER_UI_DEVICE (object);
+ gvc_mixer_ui_device_set_profiles (in, profiles);
+
+ g_hash_table_insert (control->priv->ui_inputs,
+ GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (in)),
+ g_object_ref (in));
+ object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
+ "type", UIDeviceOutput,
+ "description", gvc_mixer_card_get_name (card),
+ "origin", "", /* Leave it empty for these special cases */
+ "port-name", NULL,
+ "port-available", FALSE,
+ "card", card,
+ NULL);
+ out = GVC_MIXER_UI_DEVICE (object);
+ gvc_mixer_ui_device_set_profiles (out, profiles);
+
+ g_hash_table_insert (control->priv->ui_outputs,
+ GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (out)),
+ g_object_ref (out));
+}
+
+#ifdef HAVE_ALSA
+typedef struct {
+ char *port_name_to_set;
+ guint32 headset_card;
+} PortStatusData;
+
+static void
+port_status_data_free (PortStatusData *data)
+{
+ if (data == NULL)
+ return;
+ g_free (data->port_name_to_set);
+ g_free (data);
+}
+
+/*
+ We need to re-enumerate sources and sinks every time the user makes a choice,
+ because they can change due to use interaction in other software (or policy
+ changes inside PulseAudio). Enumeration means PulseAudio will do a series of
+ callbacks, one for every source/sink.
+ Set the port when we find the correct source/sink.
+ */
+
+static void
+sink_info_cb (pa_context *c,
+ const pa_sink_info *i,
+ int eol,
+ void *userdata)
+{
+ PortStatusData *data = userdata;
+ pa_operation *o;
+ guint j;
+ const char *s;
+
+ if (eol != 0) {
+ port_status_data_free (data);
+ return;
+ }
+
+ if (i->card != data->headset_card)
+ return;
+
+ s = data->port_name_to_set;
+
+ if (i->active_port &&
+ strcmp (i->active_port->name, s) == 0)
+ return;
+
+ for (j = 0; j < i->n_ports; j++)
+ if (strcmp (i->ports[j]->name, s) == 0)
+ break;
+
+ if (j >= i->n_ports)
+ return;
+
+ o = pa_context_set_sink_port_by_index (c, i->index, s, NULL, NULL);
+ g_clear_pointer (&o, pa_operation_unref);
+}
+
+static void
+source_info_cb (pa_context *c,
+ const pa_source_info *i,
+ int eol,
+ void *userdata)
+{
+ PortStatusData *data = userdata;
+ pa_operation *o;
+ guint j;
+ const char *s;
+
+ if (eol != 0) {
+ port_status_data_free (data);
+ return;
+ }
+
+ if (i->card != data->headset_card)
+ return;
+
+ s = data->port_name_to_set;
+
+ for (j = 0; j < i->n_ports; j++) {
+ if (g_str_equal (i->ports[j]->name, s)) {
+ o = pa_context_set_default_source (c,
+ i->name,
+ NULL,
+ NULL);
+ if (o == NULL) {
+ g_warning ("pa_context_set_default_source() failed");
+ return;
+ }
+ }
+ }
+
+ if (i->active_port && strcmp (i->active_port->name, s) == 0)
+ return;
+
+ for (j = 0; j < i->n_ports; j++)
+ if (strcmp (i->ports[j]->name, s) == 0)
+ break;
+
+ if (j >= i->n_ports)
+ return;
+
+ o = pa_context_set_source_port_by_index(c, i->index, s, NULL, NULL);
+ g_clear_pointer (&o, pa_operation_unref);
+}
+
+static void
+gvc_mixer_control_set_port_status_for_headset (GvcMixerControl *control,
+ guint id,
+ const char *port_name,
+ gboolean is_output)
+{
+ pa_operation *o;
+ PortStatusData *data;
+
+ if (port_name == NULL)
+ return;
+
+ data = g_new0 (PortStatusData, 1);
+ data->port_name_to_set = g_strdup (port_name);
+ data->headset_card = id;
+
+ if (is_output)
+ o = pa_context_get_sink_info_list (control->priv->pa_context, sink_info_cb, data);
+ else
+ o = pa_context_get_source_info_list (control->priv->pa_context, source_info_cb, data);
+
+ g_clear_pointer (&o, pa_operation_unref);
+}
+#endif /* HAVE_ALSA */
+
+static void
+free_priv_port_names (GvcMixerControl *control)
+{
+#ifdef HAVE_ALSA
+ g_clear_pointer (&control->priv->headphones_name, g_free);
+ g_clear_pointer (&control->priv->headsetmic_name, g_free);
+ g_clear_pointer (&control->priv->headphonemic_name, g_free);
+ g_clear_pointer (&control->priv->internalspk_name, g_free);
+ g_clear_pointer (&control->priv->internalmic_name, g_free);
+#endif
+}
+
+void
+gvc_mixer_control_set_headset_port (GvcMixerControl *control,
+ guint id,
+ GvcHeadsetPortChoice choice)
+{
+ g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
+
+#ifdef HAVE_ALSA
+ switch (choice) {
+ case GVC_HEADSET_PORT_CHOICE_HEADPHONES:
+ gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headphones_name, TRUE);
+ gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->internalmic_name, FALSE);
+ break;
+ case GVC_HEADSET_PORT_CHOICE_HEADSET:
+ gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headphones_name, TRUE);
+ gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headsetmic_name, FALSE);
+ break;
+ case GVC_HEADSET_PORT_CHOICE_MIC:
+ gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->internalspk_name, TRUE);
+ gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headphonemic_name, FALSE);
+ break;
+ case GVC_HEADSET_PORT_CHOICE_NONE:
+ default:
+ g_assert_not_reached ();
+ }
+#else
+ g_warning ("BUG: libgnome-volume-control compiled without ALSA support");
+#endif /* HAVE_ALSA */
+}
+
+#ifdef HAVE_ALSA
+typedef struct {
+ const pa_card_port_info *headphones;
+ const pa_card_port_info *headsetmic;
+ const pa_card_port_info *headphonemic;
+ const pa_card_port_info *internalmic;
+ const pa_card_port_info *internalspk;
+} headset_ports;
+
+/*
+ In PulseAudio without ucm, ports will show up with the following names:
+ Headphones - analog-output-headphones
+ Headset mic - analog-input-headset-mic (was: analog-input-microphone-headset)
+ Jack in mic-in mode - analog-input-headphone-mic (was: analog-input-microphone)
+
+ However, since regular mics also show up as analog-input-microphone,
+ we need to check for certain controls on alsa mixer level too, to know
+ if we deal with a separate mic jack, or a multi-function jack with a
+ mic-in mode (also called "headphone mic").
+ We check for the following names:
+
+ Headphone Mic Jack - indicates headphone and mic-in mode share the same jack,
+ i e, not two separate jacks. Hardware cannot distinguish between a
+ headphone and a mic.
+ Headset Mic Phantom Jack - indicates headset jack where hardware can not
+ distinguish between headphones and headsets
+ Headset Mic Jack - indicates headset jack where hardware can distinguish
+ between headphones and headsets. There is no use popping up a dialog in
+ this case, unless we already need to do this for the mic-in mode.
+
+ From the PA_PROCOTOL_VERSION=34, The device_port structure adds 2 members
+ availability_group and type, with the help of these 2 members, we could
+ consolidate the port checking and port setting for non-ucm and with-ucm
+ cases.
+*/
+
+#define HEADSET_PORT_SET(dst, src) \
+ do { \
+ if (!(dst) || (dst)->priority < (src)->priority) \
+ dst = src; \
+ } while (0)
+
+#define GET_PORT_NAME(x) (x ? g_strdup (x->name) : NULL)
+
+static headset_ports *
+get_headset_ports (GvcMixerControl *control,
+ const pa_card_info *c)
+{
+ headset_ports *h;
+ guint i;
+
+ h = g_new0 (headset_ports, 1);
+
+ for (i = 0; i < c->n_ports; i++) {
+ pa_card_port_info *p = c->ports[i];
+ if (control->priv->server_protocol_version < 34) {
+ if (g_str_equal (p->name, "analog-output-headphones"))
+ h->headphones = p;
+ else if (g_str_equal (p->name, "analog-input-headset-mic"))
+ h->headsetmic = p;
+ else if (g_str_equal (p->name, "analog-input-headphone-mic"))
+ h->headphonemic = p;
+ else if (g_str_equal (p->name, "analog-input-internal-mic"))
+ h->internalmic = p;
+ else if (g_str_equal (p->name, "analog-output-speaker"))
+ h->internalspk = p;
+ } else {
+#if (PA_PROTOCOL_VERSION >= 34)
+ /* in the first loop, set only headphones */
+ /* the microphone ports are assigned in the second loop */
+ if (p->type == PA_DEVICE_PORT_TYPE_HEADPHONES) {
+ if (p->availability_group)
+ HEADSET_PORT_SET (h->headphones, p);
+ } else if (p->type == PA_DEVICE_PORT_TYPE_SPEAKER) {
+ HEADSET_PORT_SET (h->internalspk, p);
+ } else if (p->type == PA_DEVICE_PORT_TYPE_MIC) {
+ if (!p->availability_group)
+ HEADSET_PORT_SET (h->internalmic, p);
+ }
+#else
+ g_warning_once ("libgnome-volume-control running against PulseAudio %u, "
+ "but compiled against older %d, report a bug to your distribution",
+ control->priv->server_protocol_version,
+ PA_PROTOCOL_VERSION);
+#endif
+ }
+ }
+
+#if (PA_PROTOCOL_VERSION >= 34)
+ if (h->headphones && (control->priv->server_protocol_version >= 34)) {
+ for (i = 0; i < c->n_ports; i++) {
+ pa_card_port_info *p = c->ports[i];
+ if (g_strcmp0(h->headphones->availability_group, p->availability_group))
+ continue;
+ if (p->direction != PA_DIRECTION_INPUT)
+ continue;
+ if (p->type == PA_DEVICE_PORT_TYPE_HEADSET)
+ HEADSET_PORT_SET (h->headsetmic, p);
+ else if (p->type == PA_DEVICE_PORT_TYPE_MIC)
+ HEADSET_PORT_SET (h->headphonemic, p);
+ }
+ }
+#endif
+
+ return h;
+}
+
+static gboolean
+verify_alsa_card (int cardindex,
+ gboolean *headsetmic,
+ gboolean *headphonemic)
+{
+ char *ctlstr;
+ snd_hctl_t *hctl;
+ snd_ctl_elem_id_t *id;
+ int err;
+
+ *headsetmic = FALSE;
+ *headphonemic = FALSE;
+
+ ctlstr = g_strdup_printf ("hw:%i", cardindex);
+ if ((err = snd_hctl_open (&hctl, ctlstr, 0)) < 0) {
+ g_warning ("snd_hctl_open failed: %s", snd_strerror(err));
+ g_free (ctlstr);
+ return FALSE;
+ }
+ g_free (ctlstr);
+
+ if ((err = snd_hctl_load (hctl)) < 0) {
+ g_warning ("snd_hctl_load failed: %s", snd_strerror(err));
+ snd_hctl_close (hctl);
+ return FALSE;
+ }
+
+ snd_ctl_elem_id_alloca (&id);
+
+ snd_ctl_elem_id_clear (id);
+ snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD);
+ snd_ctl_elem_id_set_name (id, "Headphone Mic Jack");
+ if (snd_hctl_find_elem (hctl, id))
+ *headphonemic = TRUE;
+
+ snd_ctl_elem_id_clear (id);
+ snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD);
+ snd_ctl_elem_id_set_name (id, "Headset Mic Phantom Jack");
+ if (snd_hctl_find_elem (hctl, id))
+ *headsetmic = TRUE;
+
+ if (*headphonemic) {
+ snd_ctl_elem_id_clear (id);
+ snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD);
+ snd_ctl_elem_id_set_name (id, "Headset Mic Jack");
+ if (snd_hctl_find_elem (hctl, id))
+ *headsetmic = TRUE;
+ }
+
+ snd_hctl_close (hctl);
+ return *headsetmic || *headphonemic;
+}
+
+static void
+check_audio_device_selection_needed (GvcMixerControl *control,
+ const pa_card_info *info)
+{
+ headset_ports *h;
+ gboolean start_dialog, stop_dialog;
+
+ start_dialog = FALSE;
+ stop_dialog = FALSE;
+ h = get_headset_ports (control, info);
+
+ if (!h->headphones ||
+ (!h->headsetmic && !h->headphonemic)) {
+ /* Not a headset jack */
+ goto out;
+ }
+
+ if (control->priv->headset_card != (int) info->index) {
+ int cardindex;
+ gboolean hsmic = TRUE;
+ gboolean hpmic = TRUE;
+ const char *s;
+
+ s = pa_proplist_gets (info->proplist, "alsa.card");
+ if (!s)
+ goto out;
+
+ cardindex = strtol (s, NULL, 10);
+ if (cardindex == 0 && strcmp(s, "0") != 0)
+ goto out;
+
+ if (control->priv->server_protocol_version < 34) {
+ if (!verify_alsa_card(cardindex, &hsmic, &hpmic))
+ goto out;
+ }
+
+ control->priv->headset_card = info->index;
+ control->priv->has_headsetmic = hsmic && h->headsetmic;
+ control->priv->has_headphonemic = hpmic && h->headphonemic;
+ } else {
+ start_dialog = (h->headphones->available != PA_PORT_AVAILABLE_NO) && !control->priv->headset_plugged_in;
+ stop_dialog = (h->headphones->available == PA_PORT_AVAILABLE_NO) && control->priv->headset_plugged_in;
+ }
+
+ control->priv->headset_plugged_in = h->headphones->available != PA_PORT_AVAILABLE_NO;
+ free_priv_port_names (control);
+ control->priv->headphones_name = GET_PORT_NAME(h->headphones);
+ control->priv->headsetmic_name = GET_PORT_NAME(h->headsetmic);
+ control->priv->headphonemic_name = GET_PORT_NAME(h->headphonemic);
+ control->priv->internalspk_name = GET_PORT_NAME(h->internalspk);
+ control->priv->internalmic_name = GET_PORT_NAME(h->internalmic);
+
+ if (!start_dialog &&
+ !stop_dialog)
+ goto out;
+
+ if (stop_dialog) {
+ g_signal_emit (G_OBJECT (control),
+ signals[AUDIO_DEVICE_SELECTION_NEEDED],
+ 0,
+ info->index,
+ FALSE,
+ GVC_HEADSET_PORT_CHOICE_NONE);
+ } else {
+ GvcHeadsetPortChoice choices;
+
+ choices = GVC_HEADSET_PORT_CHOICE_HEADPHONES;
+ if (control->priv->has_headsetmic)
+ choices |= GVC_HEADSET_PORT_CHOICE_HEADSET;
+ if (control->priv->has_headphonemic)
+ choices |= GVC_HEADSET_PORT_CHOICE_MIC;
+
+ g_signal_emit (G_OBJECT (control),
+ signals[AUDIO_DEVICE_SELECTION_NEEDED],
+ 0,
+ info->index,
+ TRUE,
+ choices);
+ }
+
+out:
+ g_free (h);
+}
+#endif /* HAVE_ALSA */
+
+/*
+ * At this point we can determine all devices available to us (besides network 'ports')
+ * This is done by the following:
+ *
+ * - gvc_mixer_card and gvc_mixer_card_ports are created and relevant setters are called.
+ * - First it checks to see if it's a portless card. Bluetooth devices are portless AFAIHS.
+ * If so it creates two devices, an input and an output.
+ * - If it's a 'normal' card with ports it will create a new ui-device or
+ * synchronise port availability with the existing device cached for that port on this card. */
+
+static void
+update_card (GvcMixerControl *control,
+ const pa_card_info *info)
+{
+ const GList *card_ports = NULL;
+ const GList *m = NULL;
+ GvcMixerCard *card;
+ gboolean is_new = FALSE;
+#if 1
+ guint i;
+ const char *key;
+ void *state;
+
+ g_debug ("Updating card %s (index: %u driver: %s):",
+ info->name, info->index, info->driver);
+
+ for (i = 0; i < info->n_profiles; i++) {
+ struct pa_card_profile_info pi = info->profiles[i];
+ gboolean is_default;
+
+ is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
+ g_debug ("\tProfile '%s': %d sources %d sinks%s",
+ pi.name, pi.n_sources, pi.n_sinks,
+ is_default ? " (Current)" : "");
+ }
+ state = NULL;
+ key = pa_proplist_iterate (info->proplist, &state);
+ while (key != NULL) {
+ g_debug ("\tProperty: '%s' = '%s'",
+ key, pa_proplist_gets (info->proplist, key));
+ key = pa_proplist_iterate (info->proplist, &state);
+ }
+#endif
+ card = g_hash_table_lookup (control->priv->cards,
+ GUINT_TO_POINTER (info->index));
+ if (card == NULL) {
+ GList *profile_list = NULL;
+ GList *port_list = NULL;
+
+ for (i = 0; i < info->n_profiles; i++) {
+ GvcMixerCardProfile *profile;
+ struct pa_card_profile_info pi = info->profiles[i];
+
+ profile = g_new0 (GvcMixerCardProfile, 1);
+ profile->profile = g_strdup (pi.name);
+ profile->human_profile = g_strdup (pi.description);
+ profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
+ profile->n_sinks = pi.n_sinks;
+ profile->n_sources = pi.n_sources;
+ profile->priority = pi.priority;
+ profile_list = g_list_prepend (profile_list, profile);
+ }
+ card = gvc_mixer_card_new (control->priv->pa_context,
+ info->index);
+
+ for (i = 0; i < info->n_ports; i++) {
+ GvcMixerCardPort *port;
+ port = g_new0 (GvcMixerCardPort, 1);
+ port->port = g_strdup (info->ports[i]->name);
+ port->human_port = g_strdup (info->ports[i]->description);
+ port->priority = info->ports[i]->priority;
+ port->available = info->ports[i]->available;
+ port->direction = info->ports[i]->direction;
+ port->icon_name = g_strdup (pa_proplist_gets (info->ports[i]->proplist, "device.icon_name"));
+ port->profiles = determine_profiles_for_port (info->ports[i], profile_list);
+ port_list = g_list_prepend (port_list, port);
+ }
+
+ gvc_mixer_card_set_profiles (card, profile_list);
+ gvc_mixer_card_set_ports (card, port_list);
+ is_new = TRUE;
+ }
+
+ gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
+ gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
+ gvc_mixer_card_set_profile (card, info->active_profile->name);
+
+ if (is_new) {
+ g_hash_table_insert (control->priv->cards,
+ GUINT_TO_POINTER (info->index),
+ card);
+ }
+
+ card_ports = gvc_mixer_card_get_ports (card);
+
+ if (card_ports == NULL && is_new) {
+ g_debug ("Portless card just registered - %s", gvc_mixer_card_get_name (card));
+ create_ui_device_from_card (control, card);
+ }
+
+ for (m = card_ports; m != NULL; m = m->next) {
+ GvcMixerCardPort *card_port;
+ card_port = m->data;
+ if (is_new)
+ create_ui_device_from_port (control, card_port, card);
+ else {
+ for (i = 0; i < info->n_ports; i++) {
+ if (g_strcmp0 (card_port->port, info->ports[i]->name) == 0) {
+ if ((card_port->available == PA_PORT_AVAILABLE_NO) != (info->ports[i]->available == PA_PORT_AVAILABLE_NO)) {
+ card_port->available = info->ports[i]->available;
+ g_debug ("sync port availability on card %i, card port name '%s', new available value %i",
+ gvc_mixer_card_get_index (card),
+ card_port->port,
+ card_port->available);
+ match_card_port_with_existing_device (control,
+ card_port,
+ card,
+ card_port->available != PA_PORT_AVAILABLE_NO);
+ }
+ }
+ }
+ }
+ }
+
+#ifdef HAVE_ALSA
+ check_audio_device_selection_needed (control, info);
+#endif /* HAVE_ALSA */
+
+ g_signal_emit (G_OBJECT (control),
+ signals[CARD_ADDED],
+ 0,
+ info->index);
+}
+
+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_card_info_by_index_cb (pa_context *context,
+ const pa_card_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 ("Card callback failure");
+ return;
+ }
+
+ if (eol > 0) {
+ dec_outstanding (control);
+ return;
+ }
+
+ update_card (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;
+ }
+ g_debug ("get server info");
+ 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) {
+ pa_channel_map pa_map;
+ GvcChannelMap *map;
+
+ pa_map.channels = 1;
+ pa_map.map[0] = PA_CHANNEL_POSITION_MONO;
+ map = gvc_channel_map_new_from_pa_channel_map (&pa_map);
+
+ stream = gvc_mixer_event_role_new (control->priv->pa_context,
+ info->device,
+ map);
+ 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, "emblem-system-symbolic");
+ 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);
+ /* If we don't have an event stream to restore, then
+ * set one up with a default 100% volume */
+ if (!control->priv->event_sink_input_is_set) {
+ pa_ext_stream_restore_info info;
+
+ memset (&info, 0, sizeof(info));
+ info.name = "sink-input-by-media-role:event";
+ info.volume.channels = 1;
+ info.volume.values[0] = PA_VOLUME_NORM;
+ update_event_role_stream (control, &info);
+ }
+ 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_card (GvcMixerControl *control,
+ int index)
+{
+ pa_operation *o;
+
+ if (index < 0) {
+ o = pa_context_get_card_info_list (control->priv->pa_context,
+ _pa_context_get_card_info_by_index_cb,
+ control);
+ } else {
+ o = pa_context_get_card_info_by_index (control->priv->pa_context,
+ index,
+ _pa_context_get_card_info_by_index_cb,
+ control);
+ }
+
+ if (o == NULL) {
+ g_warning ("pa_context_get_card_info_by_index() 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_card (GvcMixerControl *control,
+ guint index)
+{
+
+ GList *devices, *d;
+
+ devices = g_list_concat (g_hash_table_get_values (control->priv->ui_inputs),
+ g_hash_table_get_values (control->priv->ui_outputs));
+
+ for (d = devices; d != NULL; d = d->next) {
+ GvcMixerCard *card;
+ GvcMixerUIDevice *device = d->data;
+
+ g_object_get (G_OBJECT (device), "card", &card, NULL);
+
+ if (gvc_mixer_card_get_index (card) == index) {
+ g_signal_emit (G_OBJECT (control),
+ signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+ g_debug ("Card removal remove device %s",
+ gvc_mixer_ui_device_get_description (device));
+ g_hash_table_remove (gvc_mixer_ui_device_is_output (device) ? control->priv->ui_outputs : control->priv->ui_inputs,
+ GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)));
+ }
+ }
+
+ g_list_free (devices);
+
+ g_hash_table_remove (control->priv->cards,
+ GUINT_TO_POINTER (index));
+
+ g_signal_emit (G_OBJECT (control),
+ signals[CARD_REMOVED],
+ 0,
+ index);
+}
+
+static void
+remove_sink (GvcMixerControl *control,
+ guint index)
+{
+ GvcMixerStream *stream;
+ GvcMixerUIDevice *device;
+
+ g_debug ("Removing sink: index=%u", index);
+
+ stream = g_hash_table_lookup (control->priv->sinks,
+ GUINT_TO_POINTER (index));
+ if (stream == NULL)
+ return;
+
+ device = gvc_mixer_control_lookup_device_from_stream (control, stream);
+
+ if (device != NULL) {
+ gvc_mixer_ui_device_invalidate_stream (device);
+ if (!gvc_mixer_ui_device_has_ports (device)) {
+ g_signal_emit (G_OBJECT (control),
+ signals[OUTPUT_REMOVED],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+ } else {
+ GList *devices, *d;
+
+ devices = g_hash_table_get_values (control->priv->ui_outputs);
+
+ for (d = devices; d != NULL; d = d->next) {
+ guint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
+ device = d->data;
+ g_object_get (G_OBJECT (device),
+ "stream-id", &stream_id,
+ NULL);
+ if (stream_id == gvc_mixer_stream_get_id (stream))
+ gvc_mixer_ui_device_invalidate_stream (device);
+ }
+
+ g_list_free (devices);
+ }
+ }
+
+ 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;
+ GvcMixerUIDevice *device;
+
+ g_debug ("Removing source: index=%u", index);
+
+ stream = g_hash_table_lookup (control->priv->sources,
+ GUINT_TO_POINTER (index));
+ if (stream == NULL)
+ return;
+
+ device = gvc_mixer_control_lookup_device_from_stream (control, stream);
+
+ if (device != NULL) {
+ gvc_mixer_ui_device_invalidate_stream (device);
+ if (!gvc_mixer_ui_device_has_ports (device)) {
+ g_signal_emit (G_OBJECT (control),
+ signals[INPUT_REMOVED],
+ 0,
+ gvc_mixer_ui_device_get_id (device));
+ } else {
+ GList *devices, *d;
+
+ devices = g_hash_table_get_values (control->priv->ui_inputs);
+
+ for (d = devices; d != NULL; d = d->next) {
+ guint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
+ device = d->data;
+ g_object_get (G_OBJECT (device),
+ "stream-id", &stream_id,
+ NULL);
+ if (stream_id == gvc_mixer_stream_get_id (stream))
+ gvc_mixer_ui_device_invalidate_stream (device);
+ }
+
+ g_list_free (devices);
+ }
+ }
+
+ 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;
+
+ g_debug ("Removing sink input: index=%u", index);
+
+ 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;
+
+ g_debug ("Removing source output: index=%u", index);
+
+ 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;
+
+ case PA_SUBSCRIPTION_EVENT_CARD:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ remove_card (control, index);
+ } else {
+ req_update_card (control, index);
+ }
+ break;
+ default:
+ 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|
+ PA_SUBSCRIPTION_MASK_CARD),
+ NULL,
+ NULL);
+
+ if (o == NULL) {
+ g_warning ("pa_context_subscribe() failed");
+ return;
+ }
+ pa_operation_unref (o);
+
+ req_update_server_info (control, -1);
+ req_update_card (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->server_protocol_version = pa_context_get_server_protocol_version (control->priv->pa_context);
+
+ 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
+gvc_mixer_new_pa_context (GvcMixerControl *self)
+{
+ pa_proplist *proplist;
+
+ g_return_if_fail (self);
+ g_return_if_fail (!self->priv->pa_context);
+
+ proplist = pa_proplist_new ();
+ pa_proplist_sets (proplist,
+ PA_PROP_APPLICATION_NAME,
+ self->priv->name);
+ 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);
+
+ pa_proplist_free (proplist);
+ g_assert (self->priv->pa_context);
+}
+
+static void
+remove_all_streams (GvcMixerControl *control, GHashTable *hash_table)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ remove_stream (control, value);
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static gboolean
+idle_reconnect (gpointer data)
+{
+ GvcMixerControl *control = GVC_MIXER_CONTROL (data);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_return_val_if_fail (control, FALSE);
+
+ if (control->priv->pa_context) {
+ pa_context_unref (control->priv->pa_context);
+ control->priv->pa_context = NULL;
+ control->priv->server_protocol_version = 0;
+ gvc_mixer_new_pa_context (control);
+ }
+
+ remove_all_streams (control, control->priv->sinks);
+ remove_all_streams (control, control->priv->sources);
+ remove_all_streams (control, control->priv->sink_inputs);
+ remove_all_streams (control, control->priv->source_outputs);
+
+ g_hash_table_iter_init (&iter, control->priv->clients);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ g_hash_table_iter_remove (&iter);
+
+ gvc_mixer_control_open (control); /* cannot fail */
+
+ control->priv->reconnect_id = 0;
+ return FALSE;
+}
+
+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:
+ control->priv->state = GVC_STATE_FAILED;
+ g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);
+ if (control->priv->reconnect_id == 0)
+ control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
+ 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);
+
+ control->priv->state = GVC_STATE_CONNECTING;
+ g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);
+ res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, 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);
+
+ control->priv->state = GVC_STATE_CLOSED;
+ g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CLOSED);
+ return TRUE;
+}
+
+static void
+gvc_mixer_control_dispose (GObject *object)
+{
+ GvcMixerControl *control = GVC_MIXER_CONTROL (object);
+
+ if (control->priv->reconnect_id != 0) {
+ g_source_remove (control->priv->reconnect_id);
+ control->priv->reconnect_id = 0;
+ }
+
+ 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;
+ }
+ if (control->priv->cards != NULL) {
+ g_hash_table_destroy (control->priv->cards);
+ control->priv->cards = NULL;
+ }
+ if (control->priv->ui_outputs != NULL) {
+ g_hash_table_destroy (control->priv->ui_outputs);
+ control->priv->ui_outputs = NULL;
+ }
+ if (control->priv->ui_inputs != NULL) {
+ g_hash_table_destroy (control->priv->ui_inputs);
+ control->priv->ui_inputs = NULL;
+ }
+
+ free_priv_port_names (control);
+ G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
+}
+
+static void
+gvc_mixer_control_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GvcMixerControl *self = GVC_MIXER_CONTROL (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_free (self->priv->name);
+ self->priv->name = g_value_dup_string (value);
+ g_object_notify (G_OBJECT (self), "name");
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gvc_mixer_control_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GvcMixerControl *self = GVC_MIXER_CONTROL (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_value_set_string (value, self->priv->name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static GObject *
+gvc_mixer_control_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ GvcMixerControl *self;
+
+ object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+ self = GVC_MIXER_CONTROL (object);
+
+ gvc_mixer_new_pa_context (self);
+ self->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
+
+ 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;
+ object_class->set_property = gvc_mixer_control_set_property;
+ object_class->get_property = gvc_mixer_control_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "Name to display for this mixer control",
+ NULL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+
+ signals [STATE_CHANGED] =
+ g_signal_new ("state-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, state_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ 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 [STREAM_CHANGED] =
+ g_signal_new ("stream-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, stream_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [AUDIO_DEVICE_SELECTION_NEEDED] =
+ g_signal_new ("audio-device-selection-needed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_UINT);
+ signals [CARD_ADDED] =
+ g_signal_new ("card-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [CARD_REMOVED] =
+ g_signal_new ("card-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, card_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);
+ signals [ACTIVE_OUTPUT_UPDATE] =
+ g_signal_new ("active-output-update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, active_output_update),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [ACTIVE_INPUT_UPDATE] =
+ g_signal_new ("active-input-update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, active_input_update),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [OUTPUT_ADDED] =
+ g_signal_new ("output-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, output_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [INPUT_ADDED] =
+ g_signal_new ("input-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, input_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [OUTPUT_REMOVED] =
+ g_signal_new ("output-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, output_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+ signals [INPUT_REMOVED] =
+ g_signal_new ("input-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GvcMixerControlClass, input_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+
+static void
+gvc_mixer_control_init (GvcMixerControl *control)
+{
+ control->priv = gvc_mixer_control_get_instance_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->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+ control->priv->ui_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
+ control->priv->ui_inputs = 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);
+
+#ifdef HAVE_ALSA
+ control->priv->headset_card = -1;
+#endif /* HAVE_ALSA */
+
+ control->priv->state = GVC_STATE_CLOSED;
+}
+
+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_free (mixer_control->priv->name);
+ mixer_control->priv->name = NULL;
+
+ g_return_if_fail (mixer_control->priv != NULL);
+ G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
+}
+
+GvcMixerControl *
+gvc_mixer_control_new (const char *name)
+{
+ GObject *control;
+ control = g_object_new (GVC_TYPE_MIXER_CONTROL,
+ "name", name,
+ NULL);
+ return GVC_MIXER_CONTROL (control);
+}
+
+gdouble
+gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), 0);
+ return (gdouble) PA_VOLUME_NORM;
+}
+
+gdouble
+gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), 0);
+ return (gdouble) PA_VOLUME_UI_MAX;
+}
diff --git a/subprojects/gvc/gvc-mixer-control.h b/subprojects/gvc/gvc-mixer-control.h
new file mode 100644
index 0000000..8137849
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-control.h
@@ -0,0 +1,155 @@
+/* -*- 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 "gvc-mixer-stream.h"
+#include "gvc-mixer-card.h"
+#include "gvc-mixer-ui-device.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ GVC_STATE_CLOSED,
+ GVC_STATE_READY,
+ GVC_STATE_CONNECTING,
+ GVC_STATE_FAILED
+} GvcMixerControlState;
+
+typedef enum
+{
+ GVC_HEADSET_PORT_CHOICE_NONE = 0,
+ GVC_HEADSET_PORT_CHOICE_HEADPHONES = 1 << 0,
+ GVC_HEADSET_PORT_CHOICE_HEADSET = 1 << 1,
+ GVC_HEADSET_PORT_CHOICE_MIC = 1 << 2
+} GvcHeadsetPortChoice;
+
+#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 (*state_changed) (GvcMixerControl *control,
+ GvcMixerControlState new_state);
+ void (*stream_added) (GvcMixerControl *control,
+ guint id);
+ void (*stream_changed) (GvcMixerControl *control,
+ guint id);
+ void (*stream_removed) (GvcMixerControl *control,
+ guint id);
+ void (*card_added) (GvcMixerControl *control,
+ guint id);
+ void (*card_removed) (GvcMixerControl *control,
+ guint id);
+ void (*default_sink_changed) (GvcMixerControl *control,
+ guint id);
+ void (*default_source_changed) (GvcMixerControl *control,
+ guint id);
+ void (*active_output_update) (GvcMixerControl *control,
+ guint id);
+ void (*active_input_update) (GvcMixerControl *control,
+ guint id);
+ void (*output_added) (GvcMixerControl *control,
+ guint id);
+ void (*input_added) (GvcMixerControl *control,
+ guint id);
+ void (*output_removed) (GvcMixerControl *control,
+ guint id);
+ void (*input_removed) (GvcMixerControl *control,
+ guint id);
+ void (*audio_device_selection_needed)
+ (GvcMixerControl *control,
+ guint id,
+ gboolean show_dialog,
+ GvcHeadsetPortChoice choices);
+} GvcMixerControlClass;
+
+GType gvc_mixer_control_get_type (void);
+
+GvcMixerControl * gvc_mixer_control_new (const char *name);
+
+gboolean gvc_mixer_control_open (GvcMixerControl *control);
+gboolean gvc_mixer_control_close (GvcMixerControl *control);
+
+GSList * gvc_mixer_control_get_cards (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);
+GvcMixerCard * gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
+ guint id);
+GvcMixerUIDevice * gvc_mixer_control_lookup_output_id (GvcMixerControl *control,
+ guint id);
+GvcMixerUIDevice * gvc_mixer_control_lookup_input_id (GvcMixerControl *control,
+ guint id);
+GvcMixerUIDevice * gvc_mixer_control_lookup_device_from_stream (GvcMixerControl *control,
+ GvcMixerStream *stream);
+
+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);
+
+gdouble gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control);
+gdouble gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control);
+void gvc_mixer_control_change_output (GvcMixerControl *control,
+ GvcMixerUIDevice* output);
+void gvc_mixer_control_change_input (GvcMixerControl *control,
+ GvcMixerUIDevice* input);
+GvcMixerStream* gvc_mixer_control_get_stream_from_device (GvcMixerControl *control,
+ GvcMixerUIDevice *device);
+gboolean gvc_mixer_control_change_profile_on_selected_device (GvcMixerControl *control,
+ GvcMixerUIDevice *device,
+ const gchar* profile);
+
+void gvc_mixer_control_set_headset_port (GvcMixerControl *control,
+ guint id,
+ GvcHeadsetPortChoice choices);
+
+GvcMixerControlState gvc_mixer_control_get_state (GvcMixerControl *control);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_CONTROL_H */
diff --git a/subprojects/gvc/gvc-mixer-event-role.c b/subprojects/gvc/gvc-mixer-event-role.c
new file mode 100644
index 0000000..9f5e26a
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-event-role.c
@@ -0,0 +1,228 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/ext-stream-restore.h>
+
+#include "gvc-mixer-event-role.h"
+#include "gvc-mixer-stream-private.h"
+#include "gvc-channel-map-private.h"
+
+struct GvcMixerEventRolePrivate
+{
+ char *device;
+};
+
+enum
+{
+ PROP_0,
+ PROP_DEVICE
+};
+
+static void gvc_mixer_event_role_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerEventRole, gvc_mixer_event_role, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+update_settings (GvcMixerEventRole *role,
+ gboolean is_muted,
+ gpointer *op)
+{
+ pa_operation *o;
+ const GvcChannelMap *map;
+ pa_context *context;
+ pa_ext_stream_restore_info info;
+
+ map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role));
+
+ info.volume = *gvc_channel_map_get_cvolume(map);
+ info.name = "sink-input-by-media-role:event";
+ info.channel_map = *gvc_channel_map_get_pa_channel_map(map);
+ 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;
+ }
+
+ if (op != NULL)
+ *op = o;
+
+ return TRUE;
+}
+
+static gboolean
+gvc_mixer_event_role_push_volume (GvcMixerStream *stream, gpointer *op)
+{
+ return update_settings (GVC_MIXER_EVENT_ROLE (stream),
+ gvc_mixer_stream_get_is_muted (stream), op);
+}
+
+static gboolean
+gvc_mixer_event_role_change_is_muted (GvcMixerStream *stream,
+ gboolean is_muted)
+{
+ /* Apply change straight away so that we don't get a race with
+ * gvc_mixer_event_role_push_volume().
+ * See https://bugs.freedesktop.org/show_bug.cgi?id=51413 */
+ gvc_mixer_stream_set_is_muted (stream, is_muted);
+ return update_settings (GVC_MIXER_EVENT_ROLE (stream),
+ is_muted, NULL);
+}
+
+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 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->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->push_volume = gvc_mixer_event_role_push_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));
+}
+
+static void
+gvc_mixer_event_role_init (GvcMixerEventRole *event_role)
+{
+ event_role->priv = gvc_mixer_event_role_get_instance_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);
+}
+
+/**
+ * gvc_mixer_event_role_new: (skip)
+ * @context:
+ * @device:
+ * @channel_map:
+ *
+ * Returns:
+ */
+GvcMixerStream *
+gvc_mixer_event_role_new (pa_context *context,
+ const char *device,
+ GvcChannelMap *channel_map)
+{
+ GObject *object;
+
+ object = g_object_new (GVC_TYPE_MIXER_EVENT_ROLE,
+ "pa-context", context,
+ "index", 0,
+ "device", device,
+ "channel-map", channel_map,
+ NULL);
+
+ return GVC_MIXER_STREAM (object);
+}
diff --git a/subprojects/gvc/gvc-mixer-event-role.h b/subprojects/gvc/gvc-mixer-event-role.h
new file mode 100644
index 0000000..ab4c509
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-event-role.h
@@ -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_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,
+ GvcChannelMap *channel_map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_EVENT_ROLE_H */
diff --git a/subprojects/gvc/gvc-mixer-sink-input.c b/subprojects/gvc/gvc-mixer-sink-input.c
new file mode 100644
index 0000000..a359daf
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-sink-input.c
@@ -0,0 +1,159 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-sink-input.h"
+#include "gvc-mixer-stream-private.h"
+#include "gvc-channel-map-private.h"
+
+struct GvcMixerSinkInputPrivate
+{
+ gpointer dummy;
+};
+
+static void gvc_mixer_sink_input_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerSinkInput, gvc_mixer_sink_input, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op)
+{
+ pa_operation *o;
+ guint index;
+ const GvcChannelMap *map;
+ pa_context *context;
+ const pa_cvolume *cv;
+
+ index = gvc_mixer_stream_get_index (stream);
+
+ map = gvc_mixer_stream_get_channel_map (stream);
+
+ cv = gvc_channel_map_get_cvolume(map);
+
+ 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;
+ }
+
+ *op = 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 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->finalize = gvc_mixer_sink_input_finalize;
+
+ stream_class->push_volume = gvc_mixer_sink_input_push_volume;
+ stream_class->change_is_muted = gvc_mixer_sink_input_change_is_muted;
+}
+
+static void
+gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input)
+{
+ sink_input->priv = gvc_mixer_sink_input_get_instance_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);
+}
+
+/**
+ * gvc_mixer_sink_input_new: (skip)
+ * @context:
+ * @index:
+ * @channel_map:
+ *
+ * Returns:
+ */
+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);
+}
diff --git a/subprojects/gvc/gvc-mixer-sink-input.h b/subprojects/gvc/gvc-mixer-sink-input.h
new file mode 100644
index 0000000..17bf127
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-sink-input.h
@@ -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 *channel_map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SINK_INPUT_H */
diff --git a/subprojects/gvc/gvc-mixer-sink.c b/subprojects/gvc/gvc-mixer-sink.c
new file mode 100644
index 0000000..a6115c6
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-sink.c
@@ -0,0 +1,189 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-sink.h"
+#include "gvc-mixer-stream-private.h"
+#include "gvc-channel-map-private.h"
+
+struct GvcMixerSinkPrivate
+{
+ gpointer dummy;
+};
+
+static void gvc_mixer_sink_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerSink, gvc_mixer_sink, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_sink_push_volume (GvcMixerStream *stream, gpointer *op)
+{
+ pa_operation *o;
+ guint index;
+ const GvcChannelMap *map;
+ pa_context *context;
+ const pa_cvolume *cv;
+
+ index = gvc_mixer_stream_get_index (stream);
+
+ map = gvc_mixer_stream_get_channel_map (stream);
+
+ /* set the volume */
+ cv = gvc_channel_map_get_cvolume(map);
+
+ 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: %s", pa_strerror(pa_context_errno(context)));
+ return FALSE;
+ }
+
+ *op = 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: %s", pa_strerror(pa_context_errno(context)));
+ return FALSE;
+ }
+
+ pa_operation_unref(o);
+
+ return TRUE;
+}
+
+static gboolean
+gvc_mixer_sink_change_port (GvcMixerStream *stream,
+ const char *port)
+{
+ 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_port_by_index (context,
+ index,
+ port,
+ NULL,
+ NULL);
+
+ if (o == NULL) {
+ g_warning ("pa_context_set_sink_port_by_index() failed: %s", pa_strerror(pa_context_errno(context)));
+ return FALSE;
+ }
+
+ pa_operation_unref(o);
+
+ return TRUE;
+}
+
+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->finalize = gvc_mixer_sink_finalize;
+
+ stream_class->push_volume = gvc_mixer_sink_push_volume;
+ stream_class->change_port = gvc_mixer_sink_change_port;
+ stream_class->change_is_muted = gvc_mixer_sink_change_is_muted;
+}
+
+static void
+gvc_mixer_sink_init (GvcMixerSink *sink)
+{
+ sink->priv = gvc_mixer_sink_get_instance_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);
+}
+
+/**
+ * gvc_mixer_sink_new: (skip)
+ * @context:
+ * @index:
+ * @channel_map:
+ *
+ * Returns:
+ */
+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);
+}
diff --git a/subprojects/gvc/gvc-mixer-sink.h b/subprojects/gvc/gvc-mixer-sink.h
new file mode 100644
index 0000000..3fbe291
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-sink.h
@@ -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 *channel_map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SINK_H */
diff --git a/subprojects/gvc/gvc-mixer-source-output.c b/subprojects/gvc/gvc-mixer-source-output.c
new file mode 100644
index 0000000..c4a275a
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-source-output.c
@@ -0,0 +1,160 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-source-output.h"
+#include "gvc-mixer-stream-private.h"
+#include "gvc-channel-map-private.h"
+
+struct GvcMixerSourceOutputPrivate
+{
+ gpointer dummy;
+};
+
+static void gvc_mixer_source_output_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerSourceOutput, gvc_mixer_source_output, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_source_output_push_volume (GvcMixerStream *stream, gpointer *op)
+{
+ pa_operation *o;
+ guint index;
+ const GvcChannelMap *map;
+ pa_context *context;
+ const pa_cvolume *cv;
+
+ index = gvc_mixer_stream_get_index (stream);
+
+ map = gvc_mixer_stream_get_channel_map (stream);
+
+ cv = gvc_channel_map_get_cvolume(map);
+
+ context = gvc_mixer_stream_get_pa_context (stream);
+
+ o = pa_context_set_source_output_volume (context,
+ index,
+ cv,
+ NULL,
+ NULL);
+
+ if (o == NULL) {
+ g_warning ("pa_context_set_source_output_volume() failed");
+ return FALSE;
+ }
+
+ *op = o;
+
+ return TRUE;
+}
+
+static gboolean
+gvc_mixer_source_output_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_output_mute (context,
+ index,
+ is_muted,
+ NULL,
+ NULL);
+
+ if (o == NULL) {
+ g_warning ("pa_context_set_source_output_mute_by_index() failed");
+ return FALSE;
+ }
+
+ pa_operation_unref(o);
+
+ return TRUE;
+}
+
+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->finalize = gvc_mixer_source_output_finalize;
+
+ stream_class->push_volume = gvc_mixer_source_output_push_volume;
+ stream_class->change_is_muted = gvc_mixer_source_output_change_is_muted;
+}
+
+static void
+gvc_mixer_source_output_init (GvcMixerSourceOutput *source_output)
+{
+ source_output->priv = gvc_mixer_source_output_get_instance_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);
+}
+
+/**
+ * gvc_mixer_source_output_new: (skip)
+ * @context:
+ * @index:
+ * @channel_map:
+ *
+ * Returns:
+ */
+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);
+}
diff --git a/subprojects/gvc/gvc-mixer-source-output.h b/subprojects/gvc/gvc-mixer-source-output.h
new file mode 100644
index 0000000..4d9a6d6
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-source-output.h
@@ -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 *channel_map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SOURCE_OUTPUT_H */
diff --git a/subprojects/gvc/gvc-mixer-source.c b/subprojects/gvc/gvc-mixer-source.c
new file mode 100644
index 0000000..434eec3
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-source.c
@@ -0,0 +1,189 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-source.h"
+#include "gvc-mixer-stream-private.h"
+#include "gvc-channel-map-private.h"
+
+struct GvcMixerSourcePrivate
+{
+ gpointer dummy;
+};
+
+static void gvc_mixer_source_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerSource, gvc_mixer_source, GVC_TYPE_MIXER_STREAM)
+
+static gboolean
+gvc_mixer_source_push_volume (GvcMixerStream *stream, gpointer *op)
+{
+ pa_operation *o;
+ guint index;
+ const GvcChannelMap *map;
+ pa_context *context;
+ const pa_cvolume *cv;
+
+ index = gvc_mixer_stream_get_index (stream);
+
+ map = gvc_mixer_stream_get_channel_map (stream);
+
+ /* set the volume */
+ cv = gvc_channel_map_get_cvolume (map);
+
+ 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: %s", pa_strerror(pa_context_errno(context)));
+ return FALSE;
+ }
+
+ *op = 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: %s", pa_strerror(pa_context_errno(context)));
+ return FALSE;
+ }
+
+ pa_operation_unref(o);
+
+ return TRUE;
+}
+
+static gboolean
+gvc_mixer_source_change_port (GvcMixerStream *stream,
+ const char *port)
+{
+ 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_port_by_index (context,
+ index,
+ port,
+ NULL,
+ NULL);
+
+ if (o == NULL) {
+ g_warning ("pa_context_set_source_port_by_index() failed: %s", pa_strerror(pa_context_errno(context)));
+ return FALSE;
+ }
+
+ pa_operation_unref(o);
+
+ return TRUE;
+}
+
+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->finalize = gvc_mixer_source_finalize;
+
+ stream_class->push_volume = gvc_mixer_source_push_volume;
+ stream_class->change_is_muted = gvc_mixer_source_change_is_muted;
+ stream_class->change_port = gvc_mixer_source_change_port;
+}
+
+static void
+gvc_mixer_source_init (GvcMixerSource *source)
+{
+ source->priv = gvc_mixer_source_get_instance_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);
+}
+
+/**
+ * gvc_mixer_source_new: (skip)
+ * @context:
+ * @index:
+ * @channel_map:
+ *
+ * Returns:
+ */
+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);
+}
diff --git a/subprojects/gvc/gvc-mixer-source.h b/subprojects/gvc/gvc-mixer-source.h
new file mode 100644
index 0000000..bdffe8c
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-source.h
@@ -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 *channel_map);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_SOURCE_H */
diff --git a/subprojects/gvc/gvc-mixer-stream-private.h b/subprojects/gvc/gvc-mixer-stream-private.h
new file mode 100644
index 0000000..b97ecf5
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-stream-private.h
@@ -0,0 +1,34 @@
+/* -*- 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_PRIVATE_H
+#define __GVC_MIXER_STREAM_PRIVATE_H
+
+#include <glib-object.h>
+
+#include "gvc-channel-map.h"
+
+G_BEGIN_DECLS
+
+pa_context * gvc_mixer_stream_get_pa_context (GvcMixerStream *stream);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_STREAM_PRIVATE_H */
diff --git a/subprojects/gvc/gvc-mixer-stream.c b/subprojects/gvc/gvc-mixer-stream.c
new file mode 100644
index 0000000..c324900
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-stream.c
@@ -0,0 +1,1090 @@
+/* -*- 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-lib.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "gvc-mixer-stream.h"
+#include "gvc-mixer-stream-private.h"
+#include "gvc-channel-map-private.h"
+#include "gvc-enum-types.h"
+
+static guint32 stream_serial = 1;
+
+struct GvcMixerStreamPrivate
+{
+ pa_context *pa_context;
+ guint id;
+ guint index;
+ guint card_index;
+ GvcChannelMap *channel_map;
+ char *name;
+ char *description;
+ char *application_id;
+ char *icon_name;
+ char *form_factor;
+ char *sysfs_path;
+ gboolean is_muted;
+ gboolean can_decibel;
+ gboolean is_event_stream;
+ gboolean is_virtual;
+ pa_volume_t base_volume;
+ pa_operation *change_volume_op;
+ char *port;
+ char *human_port;
+ GList *ports;
+ GvcMixerStreamState state;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ID,
+ PROP_PA_CONTEXT,
+ PROP_CHANNEL_MAP,
+ PROP_INDEX,
+ PROP_NAME,
+ PROP_DESCRIPTION,
+ PROP_APPLICATION_ID,
+ PROP_ICON_NAME,
+ PROP_FORM_FACTOR,
+ PROP_SYSFS_PATH,
+ PROP_VOLUME,
+ PROP_DECIBEL,
+ PROP_IS_MUTED,
+ PROP_CAN_DECIBEL,
+ PROP_IS_EVENT_STREAM,
+ PROP_IS_VIRTUAL,
+ PROP_CARD_INDEX,
+ PROP_PORT,
+ PROP_STATE,
+};
+
+static void gvc_mixer_stream_finalize (GObject *object);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GvcMixerStream, gvc_mixer_stream, G_TYPE_OBJECT)
+
+static void
+free_port (GvcMixerStreamPort *p)
+{
+ g_free (p->port);
+ g_free (p->human_port);
+ g_slice_free (GvcMixerStreamPort, p);
+}
+
+static GvcMixerStreamPort *
+dup_port (GvcMixerStreamPort *p)
+{
+ GvcMixerStreamPort *m;
+
+ m = g_slice_new (GvcMixerStreamPort);
+
+ *m = *p;
+ m->port = g_strdup (p->port);
+ m->human_port = g_strdup (p->human_port);
+
+ return m;
+}
+
+G_DEFINE_BOXED_TYPE (GvcMixerStreamPort, gvc_mixer_stream_port, dup_port, free_port)
+
+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;
+}
+
+const 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;
+}
+
+/**
+ * gvc_mixer_stream_get_volume:
+ * @stream:
+ *
+ * Returns: (type guint32):
+ */
+pa_volume_t
+gvc_mixer_stream_get_volume (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+
+ return (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME];
+}
+
+gdouble
+gvc_mixer_stream_get_decibel (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+
+ return pa_sw_volume_to_dB(
+ (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]);
+}
+
+/**
+ * gvc_mixer_stream_set_volume:
+ * @stream:
+ * @volume: (type guint32):
+ *
+ * Returns:
+ */
+gboolean
+gvc_mixer_stream_set_volume (GvcMixerStream *stream,
+ pa_volume_t volume)
+{
+ pa_cvolume cv;
+
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map);
+ pa_cvolume_scale(&cv, volume);
+
+ if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) {
+ gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE);
+ g_object_notify (G_OBJECT (stream), "volume");
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gvc_mixer_stream_set_decibel (GvcMixerStream *stream,
+ gdouble db)
+{
+ pa_cvolume cv;
+
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map);
+ pa_cvolume_scale(&cv, pa_sw_volume_from_dB(db));
+
+ if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) {
+ gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE);
+ g_object_notify (G_OBJECT (stream), "volume");
+ }
+
+ 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;
+}
+
+gboolean
+gvc_mixer_stream_is_event_stream (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ return stream->priv->is_event_stream;
+}
+
+gboolean
+gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream,
+ gboolean is_event_stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ stream->priv->is_event_stream = is_event_stream;
+ g_object_notify (G_OBJECT (stream), "is-event-stream");
+
+ return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_is_virtual (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ return stream->priv->is_virtual;
+}
+
+gboolean
+gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream,
+ gboolean is_virtual)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ stream->priv->is_virtual = is_virtual;
+ g_object_notify (G_OBJECT (stream), "is-virtual");
+
+ return TRUE;
+}
+
+const char *
+gvc_mixer_stream_get_application_id (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+ return stream->priv->application_id;
+}
+
+gboolean
+gvc_mixer_stream_set_application_id (GvcMixerStream *stream,
+ const char *application_id)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ g_free (stream->priv->application_id);
+ stream->priv->application_id = g_strdup (application_id);
+ g_object_notify (G_OBJECT (stream), "application-id");
+
+ return TRUE;
+}
+
+static void
+on_channel_map_volume_changed (GvcChannelMap *channel_map,
+ gboolean set,
+ GvcMixerStream *stream)
+{
+ if (set == TRUE)
+ gvc_mixer_stream_push_volume (stream);
+
+ g_object_notify (G_OBJECT (stream), "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_volume_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,
+ "volume-changed",
+ G_CALLBACK (on_channel_map_volume_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;
+}
+
+const char *
+gvc_mixer_stream_get_form_factor (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+ return stream->priv->form_factor;
+}
+
+const char *
+gvc_mixer_stream_get_sysfs_path (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+ return stream->priv->sysfs_path;
+}
+
+/**
+ * gvc_mixer_stream_get_gicon:
+ * @stream: a #GvcMixerStream
+ *
+ * Returns: (transfer full): a new #GIcon
+ */
+GIcon *
+gvc_mixer_stream_get_gicon (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+ if (stream->priv->icon_name == NULL)
+ return NULL;
+ return g_themed_icon_new_with_default_fallbacks (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;
+}
+
+gboolean
+gvc_mixer_stream_set_form_factor (GvcMixerStream *stream,
+ const char *form_factor)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ g_free (stream->priv->form_factor);
+ stream->priv->form_factor = g_strdup (form_factor);
+ g_object_notify (G_OBJECT (stream), "form-factor");
+
+ return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_set_sysfs_path (GvcMixerStream *stream,
+ const char *sysfs_path)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ g_free (stream->priv->sysfs_path);
+ stream->priv->sysfs_path = g_strdup (sysfs_path);
+ g_object_notify (G_OBJECT (stream), "sysfs-path");
+
+ return TRUE;
+}
+
+/**
+ * gvc_mixer_stream_get_base_volume:
+ * @stream:
+ *
+ * Returns: (type guint32):
+ */
+pa_volume_t
+gvc_mixer_stream_get_base_volume (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0);
+
+ return stream->priv->base_volume;
+}
+
+/**
+ * gvc_mixer_stream_set_base_volume:
+ * @stream:
+ * @base_volume: (type guint32):
+ *
+ * Returns:
+ */
+gboolean
+gvc_mixer_stream_set_base_volume (GvcMixerStream *stream,
+ pa_volume_t base_volume)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ stream->priv->base_volume = base_volume;
+
+ return TRUE;
+}
+
+const GvcMixerStreamPort *
+gvc_mixer_stream_get_port (GvcMixerStream *stream)
+{
+ GList *l;
+
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+ g_return_val_if_fail (stream->priv->ports != NULL, NULL);
+
+ for (l = stream->priv->ports; l != NULL; l = l->next) {
+ GvcMixerStreamPort *p = l->data;
+ if (g_strcmp0 (stream->priv->port, p->port) == 0) {
+ return p;
+ }
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+gboolean
+gvc_mixer_stream_set_port (GvcMixerStream *stream,
+ const char *port)
+{
+ GList *l;
+
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+ g_return_val_if_fail (stream->priv->ports != NULL, FALSE);
+
+ g_free (stream->priv->port);
+ stream->priv->port = g_strdup (port);
+
+ g_free (stream->priv->human_port);
+ stream->priv->human_port = NULL;
+
+ for (l = stream->priv->ports; l != NULL; l = l->next) {
+ GvcMixerStreamPort *p = l->data;
+ if (g_str_equal (stream->priv->port, p->port)) {
+ stream->priv->human_port = g_strdup (p->human_port);
+ break;
+ }
+ }
+
+ g_object_notify (G_OBJECT (stream), "port");
+
+ return TRUE;
+}
+
+gboolean
+gvc_mixer_stream_change_port (GvcMixerStream *stream,
+ const char *port)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+ return GVC_MIXER_STREAM_GET_CLASS (stream)->change_port (stream, port);
+}
+
+/**
+ * gvc_mixer_stream_get_ports:
+ *
+ * Return value: (transfer none) (element-type GvcMixerStreamPort):
+ */
+const GList *
+gvc_mixer_stream_get_ports (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
+ return stream->priv->ports;
+}
+
+gboolean
+gvc_mixer_stream_set_state (GvcMixerStream *stream,
+ GvcMixerStreamState state)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ if (stream->priv->state != state) {
+ stream->priv->state = state;
+ g_object_notify (G_OBJECT (stream), "state");
+ }
+
+ return TRUE;
+}
+
+GvcMixerStreamState
+gvc_mixer_stream_get_state (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), GVC_STREAM_STATE_INVALID);
+ return stream->priv->state;
+}
+
+static int
+sort_ports (GvcMixerStreamPort *a,
+ GvcMixerStreamPort *b)
+{
+ if (a->priority == b->priority)
+ return 0;
+ if (a->priority > b->priority)
+ return 1;
+ return -1;
+}
+
+/**
+ * gvc_mixer_stream_set_ports:
+ * @ports: (transfer full) (element-type GvcMixerStreamPort):
+ */
+gboolean
+gvc_mixer_stream_set_ports (GvcMixerStream *stream,
+ GList *ports)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+ g_return_val_if_fail (stream->priv->ports == NULL, FALSE);
+
+ stream->priv->ports = g_list_sort (ports, (GCompareFunc) sort_ports);
+
+ return TRUE;
+}
+
+guint
+gvc_mixer_stream_get_card_index (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), PA_INVALID_INDEX);
+ return stream->priv->card_index;
+}
+
+gboolean
+gvc_mixer_stream_set_card_index (GvcMixerStream *stream,
+ guint card_index)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ stream->priv->card_index = card_index;
+ g_object_notify (G_OBJECT (stream), "card-index");
+
+ 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_APPLICATION_ID:
+ gvc_mixer_stream_set_application_id (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_FORM_FACTOR:
+ gvc_mixer_stream_set_form_factor (self, g_value_get_string (value));
+ break;
+ case PROP_SYSFS_PATH:
+ gvc_mixer_stream_set_sysfs_path (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_IS_EVENT_STREAM:
+ gvc_mixer_stream_set_is_event_stream (self, g_value_get_boolean (value));
+ break;
+ case PROP_IS_VIRTUAL:
+ gvc_mixer_stream_set_is_virtual (self, g_value_get_boolean (value));
+ break;
+ case PROP_CAN_DECIBEL:
+ gvc_mixer_stream_set_can_decibel (self, g_value_get_boolean (value));
+ break;
+ case PROP_PORT:
+ gvc_mixer_stream_set_port (self, g_value_get_string (value));
+ break;
+ case PROP_STATE:
+ gvc_mixer_stream_set_state (self, g_value_get_enum (value));
+ break;
+ case PROP_CARD_INDEX:
+ self->priv->card_index = g_value_get_long (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_APPLICATION_ID:
+ g_value_set_string (value, self->priv->application_id);
+ break;
+ case PROP_ICON_NAME:
+ g_value_set_string (value, self->priv->icon_name);
+ break;
+ case PROP_FORM_FACTOR:
+ g_value_set_string (value, self->priv->form_factor);
+ break;
+ case PROP_SYSFS_PATH:
+ g_value_set_string (value, self->priv->sysfs_path);
+ break;
+ case PROP_VOLUME:
+ g_value_set_ulong (value,
+ pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map)));
+ break;
+ case PROP_DECIBEL:
+ g_value_set_double (value,
+ pa_sw_volume_to_dB(pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map))));
+ break;
+ case PROP_IS_MUTED:
+ g_value_set_boolean (value, self->priv->is_muted);
+ break;
+ case PROP_IS_EVENT_STREAM:
+ g_value_set_boolean (value, self->priv->is_event_stream);
+ break;
+ case PROP_IS_VIRTUAL:
+ g_value_set_boolean (value, self->priv->is_virtual);
+ break;
+ case PROP_CAN_DECIBEL:
+ g_value_set_boolean (value, self->priv->can_decibel);
+ break;
+ case PROP_PORT:
+ g_value_set_string (value, self->priv->port);
+ break;
+ case PROP_STATE:
+ g_value_set_enum (value, self->priv->state);
+ break;
+ case PROP_CARD_INDEX:
+ g_value_set_long (value, self->priv->card_index);
+ 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_port (GvcMixerStream *stream,
+ const char *port)
+{
+ return FALSE;
+}
+
+static gboolean
+gvc_mixer_stream_real_push_volume (GvcMixerStream *stream, gpointer *op)
+{
+ return FALSE;
+}
+
+static gboolean
+gvc_mixer_stream_real_change_is_muted (GvcMixerStream *stream,
+ gboolean is_muted)
+{
+ return FALSE;
+}
+
+gboolean
+gvc_mixer_stream_push_volume (GvcMixerStream *stream)
+{
+ pa_operation *op;
+ gboolean ret;
+
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ if (stream->priv->is_event_stream != FALSE)
+ return TRUE;
+
+ g_debug ("Pushing new volume to stream '%s' (%s)",
+ stream->priv->description, stream->priv->name);
+
+ ret = GVC_MIXER_STREAM_GET_CLASS (stream)->push_volume (stream, (gpointer *) &op);
+ if (ret) {
+ if (stream->priv->change_volume_op != NULL)
+ pa_operation_unref (stream->priv->change_volume_op);
+ stream->priv->change_volume_op = op;
+ }
+ 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;
+}
+
+gboolean
+gvc_mixer_stream_is_running (GvcMixerStream *stream)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
+
+ if (stream->priv->change_volume_op == NULL)
+ return FALSE;
+
+ if ((pa_operation_get_state(stream->priv->change_volume_op) == PA_OPERATION_RUNNING))
+ return TRUE;
+
+ pa_operation_unref(stream->priv->change_volume_op);
+ stream->priv->change_volume_op = NULL;
+
+ return FALSE;
+}
+
+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->push_volume = gvc_mixer_stream_real_push_volume;
+ klass->change_port = gvc_mixer_stream_real_change_port;
+ 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_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_APPLICATION_ID,
+ g_param_spec_string ("application-id",
+ "Application identifier",
+ "Application identifier 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_FORM_FACTOR,
+ g_param_spec_string ("form-factor",
+ "Form Factor",
+ "Device form factor for this stream, as reported by PulseAudio",
+ NULL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+ g_object_class_install_property (gobject_class,
+ PROP_SYSFS_PATH,
+ g_param_spec_string ("sysfs-path",
+ "Sysfs path",
+ "Sysfs path for the device associated with 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_object_class_install_property (gobject_class,
+ PROP_IS_EVENT_STREAM,
+ g_param_spec_boolean ("is-event-stream",
+ "is event stream",
+ "Whether stream's role is to play an event",
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+ g_object_class_install_property (gobject_class,
+ PROP_IS_VIRTUAL,
+ g_param_spec_boolean ("is-virtual",
+ "is virtual stream",
+ "Whether the stream is virtual",
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+ g_object_class_install_property (gobject_class,
+ PROP_PORT,
+ g_param_spec_string ("port",
+ "Port",
+ "The name of the current port for this stream",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_STATE,
+ g_param_spec_enum ("state",
+ "State",
+ "The current state of this stream",
+ GVC_TYPE_MIXER_STREAM_STATE,
+ GVC_STREAM_STATE_INVALID,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_CARD_INDEX,
+ g_param_spec_long ("card-index",
+ "Card index",
+ "The index of the card for this stream",
+ PA_INVALID_INDEX, G_MAXLONG, PA_INVALID_INDEX,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
+}
+
+static void
+gvc_mixer_stream_init (GvcMixerStream *stream)
+{
+ stream->priv = gvc_mixer_stream_get_instance_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_object_unref (mixer_stream->priv->channel_map);
+ mixer_stream->priv->channel_map = 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->application_id);
+ mixer_stream->priv->application_id = NULL;
+
+ g_free (mixer_stream->priv->icon_name);
+ mixer_stream->priv->icon_name = NULL;
+
+ g_free (mixer_stream->priv->form_factor);
+ mixer_stream->priv->form_factor = NULL;
+
+ g_free (mixer_stream->priv->sysfs_path);
+ mixer_stream->priv->sysfs_path = NULL;
+
+ g_free (mixer_stream->priv->port);
+ mixer_stream->priv->port = NULL;
+
+ g_free (mixer_stream->priv->human_port);
+ mixer_stream->priv->human_port = NULL;
+
+ g_list_free_full (mixer_stream->priv->ports, (GDestroyNotify) free_port);
+ mixer_stream->priv->ports = NULL;
+
+ if (mixer_stream->priv->change_volume_op) {
+ pa_operation_unref(mixer_stream->priv->change_volume_op);
+ mixer_stream->priv->change_volume_op = NULL;
+ }
+
+ G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->finalize (object);
+}
diff --git a/subprojects/gvc/gvc-mixer-stream.h b/subprojects/gvc/gvc-mixer-stream.h
new file mode 100644
index 0000000..586ec75
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-stream.h
@@ -0,0 +1,146 @@
+/* -*- 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 "gvc-pulseaudio-fake.h"
+#include "gvc-channel-map.h"
+#include <gio/gio.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 (*push_volume) (GvcMixerStream *stream,
+ gpointer *operation);
+ gboolean (*change_is_muted) (GvcMixerStream *stream,
+ gboolean is_muted);
+ gboolean (*change_port) (GvcMixerStream *stream,
+ const char *port);
+} GvcMixerStreamClass;
+
+typedef struct
+{
+ char *port;
+ char *human_port;
+ guint priority;
+ gboolean available;
+} GvcMixerStreamPort;
+
+typedef enum
+{
+ GVC_STREAM_STATE_INVALID,
+ GVC_STREAM_STATE_RUNNING,
+ GVC_STREAM_STATE_IDLE,
+ GVC_STREAM_STATE_SUSPENDED
+} GvcMixerStreamState;
+
+GType gvc_mixer_stream_port_get_type (void) G_GNUC_CONST;
+GType gvc_mixer_stream_get_type (void) G_GNUC_CONST;
+
+guint gvc_mixer_stream_get_index (GvcMixerStream *stream);
+guint gvc_mixer_stream_get_id (GvcMixerStream *stream);
+const GvcChannelMap *gvc_mixer_stream_get_channel_map(GvcMixerStream *stream);
+const GvcMixerStreamPort *gvc_mixer_stream_get_port (GvcMixerStream *stream);
+const GList * gvc_mixer_stream_get_ports (GvcMixerStream *stream);
+gboolean gvc_mixer_stream_change_port (GvcMixerStream *stream,
+ const char *port);
+
+pa_volume_t gvc_mixer_stream_get_volume (GvcMixerStream *stream);
+gdouble gvc_mixer_stream_get_decibel (GvcMixerStream *stream);
+gboolean gvc_mixer_stream_push_volume (GvcMixerStream *stream);
+pa_volume_t gvc_mixer_stream_get_base_volume (GvcMixerStream *stream);
+
+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);
+gboolean gvc_mixer_stream_is_running (GvcMixerStream *stream);
+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_form_factor (GvcMixerStream *stream);
+const char * gvc_mixer_stream_get_sysfs_path (GvcMixerStream *stream);
+GIcon * gvc_mixer_stream_get_gicon (GvcMixerStream *stream);
+const char * gvc_mixer_stream_get_description (GvcMixerStream *stream);
+const char * gvc_mixer_stream_get_application_id (GvcMixerStream *stream);
+gboolean gvc_mixer_stream_is_event_stream (GvcMixerStream *stream);
+gboolean gvc_mixer_stream_is_virtual (GvcMixerStream *stream);
+guint gvc_mixer_stream_get_card_index (GvcMixerStream *stream);
+GvcMixerStreamState gvc_mixer_stream_get_state (GvcMixerStream *stream);
+
+/* private */
+gboolean gvc_mixer_stream_set_volume (GvcMixerStream *stream,
+ pa_volume_t 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);
+gboolean gvc_mixer_stream_set_form_factor (GvcMixerStream *stream,
+ const char *form_factor);
+gboolean gvc_mixer_stream_set_sysfs_path (GvcMixerStream *stream,
+ const char *sysfs_path);
+gboolean gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream,
+ gboolean is_event_stream);
+gboolean gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream,
+ gboolean is_event_stream);
+gboolean gvc_mixer_stream_set_application_id (GvcMixerStream *stream,
+ const char *application_id);
+gboolean gvc_mixer_stream_set_base_volume (GvcMixerStream *stream,
+ pa_volume_t base_volume);
+gboolean gvc_mixer_stream_set_port (GvcMixerStream *stream,
+ const char *port);
+gboolean gvc_mixer_stream_set_ports (GvcMixerStream *stream,
+ GList *ports);
+gboolean gvc_mixer_stream_set_card_index (GvcMixerStream *stream,
+ guint card_index);
+gboolean gvc_mixer_stream_set_state (GvcMixerStream *stream,
+ GvcMixerStreamState state);
+
+G_END_DECLS
+
+#endif /* __GVC_MIXER_STREAM_H */
diff --git a/subprojects/gvc/gvc-mixer-ui-device.c b/subprojects/gvc/gvc-mixer-ui-device.c
new file mode 100644
index 0000000..f7dd33e
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-ui-device.c
@@ -0,0 +1,741 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * gvc-mixer-ui-device.c
+ * Copyright (C) Conor Curran 2011 <conor.curran@canonical.com>
+ * Copyright (C) 2012 David Henningsson, Canonical Ltd. <david.henningsson@canonical.com>
+ *
+ * gvc-mixer-ui-device.c 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.
+ *
+ * gvc-mixer-ui-device.c 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "gvc-mixer-ui-device.h"
+#include "gvc-mixer-card.h"
+
+struct GvcMixerUIDevicePrivate
+{
+ gchar *first_line_desc;
+ gchar *second_line_desc;
+
+ GvcMixerCard *card;
+ gchar *port_name;
+ char *icon_name;
+ guint stream_id;
+ guint id;
+ gboolean port_available;
+
+ /* These two lists contain pointers to GvcMixerCardProfile objects. Those objects are owned by GvcMixerCard. *
+ * TODO: Do we want to add a weak reference to the GvcMixerCard for this reason? */
+ GList *supported_profiles; /* all profiles supported by this port.*/
+ GList *profiles; /* profiles to be added to combobox, subset of supported_profiles. */
+ GvcMixerUIDeviceDirection type;
+ gboolean disable_profile_swapping;
+ gchar *user_preferred_profile;
+};
+
+enum
+{
+ PROP_0,
+ PROP_DESC_LINE_1,
+ PROP_DESC_LINE_2,
+ PROP_CARD,
+ PROP_PORT_NAME,
+ PROP_STREAM_ID,
+ PROP_UI_DEVICE_TYPE,
+ PROP_PORT_AVAILABLE,
+ PROP_ICON_NAME,
+};
+
+static void gvc_mixer_ui_device_finalize (GObject *object);
+
+static void gvc_mixer_ui_device_set_icon_name (GvcMixerUIDevice *device,
+ const char *icon_name);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerUIDevice, gvc_mixer_ui_device, G_TYPE_OBJECT)
+
+static guint32
+get_next_output_serial (void)
+{
+ static guint32 output_serial = 1;
+ guint32 serial;
+
+ serial = output_serial++;
+
+ if ((gint32)output_serial < 0)
+ output_serial = 1;
+
+ return serial;
+}
+
+static void
+gvc_mixer_ui_device_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GvcMixerUIDevice *self = GVC_MIXER_UI_DEVICE (object);
+
+ switch (property_id) {
+ case PROP_DESC_LINE_1:
+ g_value_set_string (value, self->priv->first_line_desc);
+ break;
+ case PROP_DESC_LINE_2:
+ g_value_set_string (value, self->priv->second_line_desc);
+ break;
+ case PROP_CARD:
+ g_value_set_pointer (value, self->priv->card);
+ break;
+ case PROP_PORT_NAME:
+ g_value_set_string (value, self->priv->port_name);
+ break;
+ case PROP_STREAM_ID:
+ g_value_set_uint (value, self->priv->stream_id);
+ break;
+ case PROP_UI_DEVICE_TYPE:
+ g_value_set_uint (value, (guint)self->priv->type);
+ break;
+ case PROP_PORT_AVAILABLE:
+ g_value_set_boolean (value, self->priv->port_available);
+ break;
+ case PROP_ICON_NAME:
+ g_value_set_string (value, gvc_mixer_ui_device_get_icon_name (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gvc_mixer_ui_device_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GvcMixerUIDevice *self = GVC_MIXER_UI_DEVICE (object);
+
+ switch (property_id) {
+ case PROP_DESC_LINE_1:
+ g_free (self->priv->first_line_desc);
+ self->priv->first_line_desc = g_value_dup_string (value);
+ g_debug ("gvc-mixer-output-set-property - 1st line: %s",
+ self->priv->first_line_desc);
+ break;
+ case PROP_DESC_LINE_2:
+ g_free (self->priv->second_line_desc);
+ self->priv->second_line_desc = g_value_dup_string (value);
+ g_debug ("gvc-mixer-output-set-property - 2nd line: %s",
+ self->priv->second_line_desc);
+ break;
+ case PROP_CARD:
+ self->priv->card = g_value_get_pointer (value);
+ g_debug ("gvc-mixer-output-set-property - card: %p",
+ self->priv->card);
+ break;
+ case PROP_PORT_NAME:
+ g_free (self->priv->port_name);
+ self->priv->port_name = g_value_dup_string (value);
+ g_debug ("gvc-mixer-output-set-property - card port name: %s",
+ self->priv->port_name);
+ break;
+ case PROP_STREAM_ID:
+ self->priv->stream_id = g_value_get_uint (value);
+ g_debug ("gvc-mixer-output-set-property - sink/source id: %i",
+ self->priv->stream_id);
+ break;
+ case PROP_UI_DEVICE_TYPE:
+ self->priv->type = (GvcMixerUIDeviceDirection) g_value_get_uint (value);
+ break;
+ case PROP_PORT_AVAILABLE:
+ self->priv->port_available = g_value_get_boolean (value);
+ g_debug ("gvc-mixer-output-set-property - port available %i, value passed in %i",
+ self->priv->port_available, g_value_get_boolean (value));
+ break;
+ case PROP_ICON_NAME:
+ gvc_mixer_ui_device_set_icon_name (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+gvc_mixer_ui_device_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ GvcMixerUIDevice *self;
+
+ object = G_OBJECT_CLASS (gvc_mixer_ui_device_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+ self = GVC_MIXER_UI_DEVICE (object);
+ self->priv->id = get_next_output_serial ();
+ self->priv->stream_id = GVC_MIXER_UI_DEVICE_INVALID;
+ return object;
+}
+
+static void
+gvc_mixer_ui_device_init (GvcMixerUIDevice *device)
+{
+ device->priv = gvc_mixer_ui_device_get_instance_private (device);
+}
+
+static void
+gvc_mixer_ui_device_dispose (GObject *object)
+{
+ GvcMixerUIDevice *device;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GVC_MIXER_UI_DEVICE (object));
+
+ device = GVC_MIXER_UI_DEVICE (object);
+
+ g_clear_pointer (&device->priv->port_name, g_free);
+ g_clear_pointer (&device->priv->icon_name, g_free);
+ g_clear_pointer (&device->priv->first_line_desc, g_free);
+ g_clear_pointer (&device->priv->second_line_desc, g_free);
+ g_clear_pointer (&device->priv->profiles, g_list_free);
+ g_clear_pointer (&device->priv->supported_profiles, g_list_free);
+ g_clear_pointer (&device->priv->user_preferred_profile, g_free);
+
+ G_OBJECT_CLASS (gvc_mixer_ui_device_parent_class)->dispose (object);
+}
+
+static void
+gvc_mixer_ui_device_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gvc_mixer_ui_device_parent_class)->finalize (object);
+}
+
+static void
+gvc_mixer_ui_device_class_init (GvcMixerUIDeviceClass *klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *pspec;
+
+ object_class->constructor = gvc_mixer_ui_device_constructor;
+ object_class->dispose = gvc_mixer_ui_device_dispose;
+ object_class->finalize = gvc_mixer_ui_device_finalize;
+ object_class->set_property = gvc_mixer_ui_device_set_property;
+ object_class->get_property = gvc_mixer_ui_device_get_property;
+
+ pspec = g_param_spec_string ("description",
+ "Description construct prop",
+ "Set first line description",
+ "no-name-set",
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DESC_LINE_1, pspec);
+
+ pspec = g_param_spec_string ("origin",
+ "origin construct prop",
+ "Set second line description name",
+ "no-name-set",
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DESC_LINE_2, pspec);
+
+ pspec = g_param_spec_pointer ("card",
+ "Card from pulse",
+ "Set/Get card",
+ G_PARAM_READWRITE);
+
+ g_object_class_install_property (object_class, PROP_CARD, pspec);
+
+ pspec = g_param_spec_string ("port-name",
+ "port-name construct prop",
+ "Set port-name",
+ NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PORT_NAME, pspec);
+
+ pspec = g_param_spec_uint ("stream-id",
+ "stream id assigned by gvc-stream",
+ "Set/Get stream id",
+ 0,
+ G_MAXUINT,
+ GVC_MIXER_UI_DEVICE_INVALID,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_STREAM_ID, pspec);
+
+ pspec = g_param_spec_uint ("type",
+ "ui-device type",
+ "determine whether its an input and output",
+ 0, 1, 0, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_UI_DEVICE_TYPE, pspec);
+
+ pspec = g_param_spec_boolean ("port-available",
+ "available",
+ "determine whether this port is available",
+ FALSE,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PORT_AVAILABLE, pspec);
+
+ pspec = g_param_spec_string ("icon-name",
+ "Icon Name",
+ "Name of icon to display for this card",
+ NULL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_ICON_NAME, pspec);
+}
+
+/* Removes the part of the string that starts with skip_prefix
+ * ie. corresponding to the other direction.
+ * Normally either "input:" or "output:"
+ *
+ * Example: if given the input string "output:hdmi-stereo+input:analog-stereo" and
+ * skip_prefix "input:", the resulting string is "output:hdmi-stereo".
+ *
+ * The returned string must be freed with g_free().
+ */
+static gchar *
+get_profile_canonical_name (const gchar *profile_name, const gchar *skip_prefix)
+{
+ gchar *result = NULL;
+ gchar **s;
+ guint i;
+
+ /* optimisation for the simple case. */
+ if (strstr (profile_name, skip_prefix) == NULL)
+ return g_strdup (profile_name);
+
+ s = g_strsplit (profile_name, "+", 0);
+ for (i = 0; i < g_strv_length (s); i++) {
+ if (g_str_has_prefix (s[i], skip_prefix))
+ continue;
+ if (result == NULL)
+ result = g_strdup (s[i]);
+ else {
+ gchar *c = g_strdup_printf("%s+%s", result, s[i]);
+ g_free(result);
+ result = c;
+ }
+ }
+
+ g_strfreev(s);
+
+ if (!result)
+ return g_strdup("off");
+
+ return result;
+}
+
+const gchar *
+gvc_mixer_ui_device_get_matching_profile (GvcMixerUIDevice *device, const gchar *profile)
+{
+ const gchar *skip_prefix = device->priv->type == UIDeviceInput ? "output:" : "input:";
+ gchar *target_cname = get_profile_canonical_name (profile, skip_prefix);
+ GList *l;
+ gchar *result = NULL;
+
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+ g_return_val_if_fail (profile != NULL, NULL);
+
+ for (l = device->priv->profiles; l != NULL; l = l->next) {
+ gchar *canonical_name;
+ GvcMixerCardProfile* p = l->data;
+ canonical_name = get_profile_canonical_name (p->profile, skip_prefix);
+ if (strcmp (canonical_name, target_cname) == 0)
+ result = p->profile;
+ g_free (canonical_name);
+ }
+
+ g_free (target_cname);
+ g_debug ("Matching profile for '%s' is '%s'", profile, result ? result : "(null)");
+ return result;
+}
+
+
+static void
+add_canonical_names_of_profiles (GvcMixerUIDevice *device,
+ const GList *in_profiles,
+ GHashTable *added_profiles,
+ const gchar *skip_prefix,
+ gboolean only_canonical)
+{
+ const GList *l;
+
+ for (l = in_profiles; l != NULL; l = l->next) {
+ gchar *canonical_name;
+ GvcMixerCardProfile* p = l->data;
+
+ canonical_name = get_profile_canonical_name (p->profile, skip_prefix);
+ g_debug ("The canonical name for '%s' is '%s'", p->profile, canonical_name);
+
+ /* Have we already added the canonical version of this profile? */
+ if (g_hash_table_contains (added_profiles, canonical_name)) {
+ g_free (canonical_name);
+ continue;
+ }
+
+ if (only_canonical && strcmp (p->profile, canonical_name) != 0) {
+ g_free (canonical_name);
+ continue;
+ }
+
+ g_free (canonical_name);
+
+ /* https://bugzilla.gnome.org/show_bug.cgi?id=693654
+ * Don't add a profile that will make the UI device completely disappear */
+ if (p->n_sinks == 0 && p->n_sources == 0)
+ continue;
+
+ g_debug ("Adding profile to combobox: '%s' - '%s'", p->profile, p->human_profile);
+ g_hash_table_insert (added_profiles, g_strdup (p->profile), p);
+ device->priv->profiles = g_list_append (device->priv->profiles, p);
+ }
+}
+
+/**
+ * gvc_mixer_ui_device_set_profiles:
+ * @in_profiles: (element-type Gvc.MixerCardProfile): a list of GvcMixerCardProfile
+ *
+ * Assigns value to
+ * - device->priv->profiles (profiles to be added to combobox)
+ * - device->priv->supported_profiles (all profiles of this port)
+ * - device->priv->disable_profile_swapping (whether to show the combobox)
+ *
+ * This method attempts to reduce the list of profiles visible to the user by figuring out
+ * from the context of that device (whether it's an input or an output) what profiles
+ * actually provide an alternative.
+ *
+ * It does this by the following.
+ * - It ignores off profiles.
+ * - It takes the canonical name of the profile. That name is what you get when you
+ * ignore the other direction.
+ * - In the first iteration, it only adds the names of canonical profiles - i e
+ * when the other side is turned off.
+ * - Normally the first iteration covers all cases, but sometimes (e g bluetooth)
+ * it doesn't, so add other profiles whose canonical name isn't already added
+ * in a second iteration.
+ */
+void
+gvc_mixer_ui_device_set_profiles (GvcMixerUIDevice *device,
+ const GList *in_profiles)
+{
+ GHashTable *added_profiles;
+ const gchar *skip_prefix = device->priv->type == UIDeviceInput ? "output:" : "input:";
+
+ g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (device));
+
+ g_debug ("Set profiles for '%s'", gvc_mixer_ui_device_get_description(device));
+
+ if (in_profiles == NULL)
+ return;
+
+ device->priv->supported_profiles = g_list_copy ((GList*) in_profiles);
+
+ added_profiles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ /* Run two iterations: First, add profiles which are canonical themselves,
+ * Second, add profiles for which the canonical name is not added already. */
+
+ add_canonical_names_of_profiles(device, in_profiles, added_profiles, skip_prefix, TRUE);
+ add_canonical_names_of_profiles(device, in_profiles, added_profiles, skip_prefix, FALSE);
+
+ /* TODO: Consider adding the "Off" profile here */
+
+ device->priv->disable_profile_swapping = g_hash_table_size (added_profiles) <= 1;
+ g_hash_table_destroy (added_profiles);
+}
+
+/**
+ * gvc_mixer_ui_device_get_best_profile:
+ * @selected: (allow-none): The selected profile or its canonical name or %NULL for any profile
+ * @current: The currently selected profile
+ *
+ * Returns: (transfer none): a profile name, valid as long as the UI device profiles are.
+ */
+const gchar *
+gvc_mixer_ui_device_get_best_profile (GvcMixerUIDevice *device,
+ const gchar *selected,
+ const gchar *current)
+{
+ GList *candidates, *l;
+ const gchar *result;
+ const gchar *skip_prefix;
+ gchar *canonical_name_selected;
+
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+ g_return_val_if_fail (current != NULL, NULL);
+
+ if (device->priv->type == UIDeviceInput)
+ skip_prefix = "output:";
+ else
+ skip_prefix = "input:";
+
+ /* First make a list of profiles acceptable to switch to */
+ canonical_name_selected = NULL;
+ if (selected)
+ canonical_name_selected = get_profile_canonical_name (selected, skip_prefix);
+
+ candidates = NULL;
+ for (l = device->priv->supported_profiles; l != NULL; l = l->next) {
+ gchar *canonical_name;
+ GvcMixerCardProfile* p = l->data;
+ canonical_name = get_profile_canonical_name (p->profile, skip_prefix);
+ if (!canonical_name_selected || strcmp (canonical_name, canonical_name_selected) == 0) {
+ candidates = g_list_append (candidates, p);
+ g_debug ("Candidate for profile switching: '%s'", p->profile);
+ }
+ g_free (canonical_name);
+ }
+
+ if (!candidates) {
+ g_warning ("No suitable profile candidates for '%s'", selected ? selected : "(null)");
+ g_free (canonical_name_selected);
+ return current;
+ }
+
+ /* 1) Maybe we can skip profile switching altogether? */
+ result = NULL;
+ for (l = candidates; (result == NULL) && (l != NULL); l = l->next) {
+ GvcMixerCardProfile* p = l->data;
+ if (strcmp (current, p->profile) == 0)
+ result = p->profile;
+ }
+
+ /* 2) Try to keep the other side unchanged if possible */
+ if (result == NULL) {
+ guint prio = 0;
+ const gchar *skip_prefix_reverse = device->priv->type == UIDeviceInput ? "input:" : "output:";
+ gchar *current_reverse = get_profile_canonical_name (current, skip_prefix_reverse);
+ for (l = candidates; l != NULL; l = l->next) {
+ gchar *p_reverse;
+ GvcMixerCardProfile* p = l->data;
+ p_reverse = get_profile_canonical_name (p->profile, skip_prefix_reverse);
+ g_debug ("Comparing '%s' (from '%s') with '%s', prio %d", p_reverse, p->profile, current_reverse, p->priority);
+ if (strcmp (p_reverse, current_reverse) == 0 && (!result || p->priority > prio)) {
+ result = p->profile;
+ prio = p->priority;
+ }
+ g_free (p_reverse);
+ }
+ g_free (current_reverse);
+ }
+
+ /* 3) All right, let's just pick the profile with highest priority.
+ * TODO: We could consider asking a GUI question if this stops streams
+ * in the other direction */
+ if (result == NULL) {
+ guint prio = 0;
+ for (l = candidates; l != NULL; l = l->next) {
+ GvcMixerCardProfile* p = l->data;
+ if ((p->priority > prio) || !result) {
+ result = p->profile;
+ prio = p->priority;
+ }
+ }
+ }
+
+ g_list_free (candidates);
+ g_free (canonical_name_selected);
+ return result;
+}
+
+const gchar *
+gvc_mixer_ui_device_get_active_profile (GvcMixerUIDevice* device)
+{
+ GvcMixerCardProfile *profile;
+
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ if (device->priv->card == NULL) {
+ g_warning ("Device did not have an appropriate card");
+ return NULL;
+ }
+
+ profile = gvc_mixer_card_get_profile (device->priv->card);
+ return gvc_mixer_ui_device_get_matching_profile (device, profile->profile);
+}
+
+gboolean
+gvc_mixer_ui_device_should_profiles_be_hidden (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), FALSE);
+
+ return device->priv->disable_profile_swapping;
+}
+
+/**
+ * gvc_mixer_ui_device_get_profiles:
+ * @device:
+ *
+ * Returns: (transfer none) (element-type Gvc.MixerCardProfile):
+ */
+GList*
+gvc_mixer_ui_device_get_profiles (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ return device->priv->profiles;
+}
+
+/**
+ * gvc_mixer_ui_device_get_supported_profiles:
+ * @device:
+ *
+ * Returns: (transfer none) (element-type Gvc.MixerCardProfile):
+ */
+GList*
+gvc_mixer_ui_device_get_supported_profiles (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ return device->priv->supported_profiles;
+}
+
+guint
+gvc_mixer_ui_device_get_id (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), 0);
+
+ return device->priv->id;
+}
+
+guint
+gvc_mixer_ui_device_get_stream_id (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), 0);
+
+ return device->priv->stream_id;
+}
+
+void
+gvc_mixer_ui_device_invalidate_stream (GvcMixerUIDevice *self)
+{
+ g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (self));
+
+ self->priv->stream_id = GVC_MIXER_UI_DEVICE_INVALID;
+}
+
+const gchar *
+gvc_mixer_ui_device_get_description (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ return device->priv->first_line_desc;
+}
+
+const char *
+gvc_mixer_ui_device_get_icon_name (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ if (device->priv->icon_name)
+ return device->priv->icon_name;
+
+ if (device->priv->card)
+ return gvc_mixer_card_get_icon_name (device->priv->card);
+
+ return NULL;
+}
+
+static void
+gvc_mixer_ui_device_set_icon_name (GvcMixerUIDevice *device,
+ const char *icon_name)
+{
+ g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (device));
+
+ g_free (device->priv->icon_name);
+ device->priv->icon_name = g_strdup (icon_name);
+ g_object_notify (G_OBJECT (device), "icon-name");
+}
+
+
+/**
+ * gvc_mixer_ui_device_get_gicon:
+ * @device:
+ *
+ * Returns: (transfer full):
+ */
+GIcon *
+gvc_mixer_ui_device_get_gicon (GvcMixerUIDevice *device)
+{
+ const char *icon_name;
+
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ icon_name = gvc_mixer_ui_device_get_icon_name (device);
+
+ if (icon_name != NULL)
+ return g_themed_icon_new_with_default_fallbacks (icon_name);
+ else
+ return NULL;
+}
+
+const gchar *
+gvc_mixer_ui_device_get_origin (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ return device->priv->second_line_desc;
+}
+
+const gchar*
+gvc_mixer_ui_device_get_user_preferred_profile (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ return device->priv->user_preferred_profile;
+}
+
+const gchar *
+gvc_mixer_ui_device_get_top_priority_profile (GvcMixerUIDevice *device)
+{
+ GList *last;
+ GvcMixerCardProfile *profile;
+
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ last = g_list_last (device->priv->supported_profiles);
+ profile = last->data;
+
+ return profile->profile;
+}
+
+void
+gvc_mixer_ui_device_set_user_preferred_profile (GvcMixerUIDevice *device,
+ const gchar *profile)
+{
+ g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (device));
+ g_return_if_fail (profile != NULL);
+
+ g_free (device->priv->user_preferred_profile);
+ device->priv->user_preferred_profile = g_strdup (profile);
+}
+
+const gchar *
+gvc_mixer_ui_device_get_port (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
+
+ return device->priv->port_name;
+}
+
+gboolean
+gvc_mixer_ui_device_has_ports (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), FALSE);
+
+ return (device->priv->port_name != NULL);
+}
+
+gboolean
+gvc_mixer_ui_device_is_output (GvcMixerUIDevice *device)
+{
+ g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), FALSE);
+
+ return (device->priv->type == UIDeviceOutput);
+}
diff --git a/subprojects/gvc/gvc-mixer-ui-device.h b/subprojects/gvc/gvc-mixer-ui-device.h
new file mode 100644
index 0000000..69095cb
--- /dev/null
+++ b/subprojects/gvc/gvc-mixer-ui-device.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * Copyright (C) Conor Curran 2011 <conor.curran@canonical.com>
+ *
+ * This 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.
+ *
+ * gvc-mixer-ui-device.h 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GVC_MIXER_UI_DEVICE_H_
+#define _GVC_MIXER_UI_DEVICE_H_
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GVC_TYPE_MIXER_UI_DEVICE (gvc_mixer_ui_device_get_type ())
+#define GVC_MIXER_UI_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GVC_TYPE_MIXER_UI_DEVICE, GvcMixerUIDevice))
+#define GVC_MIXER_UI_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GVC_TYPE_MIXER_UI_DEVICE, GvcMixerUIDeviceClass))
+#define GVC_IS_MIXER_UI_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GVC_TYPE_MIXER_UI_DEVICE))
+#define GVC_IS_MIXER_UI_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GVC_TYPE_MIXER_UI_DEVICE))
+#define GVC_MIXER_UI_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GVC_TYPE_MIXER_UI_DEVICE, GvcMixerUIDeviceClass))
+
+#define GVC_MIXER_UI_DEVICE_INVALID 0
+
+typedef struct GvcMixerUIDevicePrivate GvcMixerUIDevicePrivate;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} GvcMixerUIDeviceClass;
+
+typedef struct
+{
+ GObject parent_instance;
+ GvcMixerUIDevicePrivate *priv;
+} GvcMixerUIDevice;
+
+typedef enum
+{
+ UIDeviceInput,
+ UIDeviceOutput,
+} GvcMixerUIDeviceDirection;
+
+GType gvc_mixer_ui_device_get_type (void) G_GNUC_CONST;
+
+guint gvc_mixer_ui_device_get_id (GvcMixerUIDevice *device);
+guint gvc_mixer_ui_device_get_stream_id (GvcMixerUIDevice *device);
+const gchar * gvc_mixer_ui_device_get_description (GvcMixerUIDevice *device);
+const gchar * gvc_mixer_ui_device_get_icon_name (GvcMixerUIDevice *device);
+GIcon * gvc_mixer_ui_device_get_gicon (GvcMixerUIDevice *device);
+const gchar * gvc_mixer_ui_device_get_origin (GvcMixerUIDevice *device);
+const gchar * gvc_mixer_ui_device_get_port (GvcMixerUIDevice *device);
+const gchar * gvc_mixer_ui_device_get_best_profile (GvcMixerUIDevice *device,
+ const gchar *selected,
+ const gchar *current);
+const gchar * gvc_mixer_ui_device_get_active_profile (GvcMixerUIDevice* device);
+const gchar * gvc_mixer_ui_device_get_matching_profile (GvcMixerUIDevice *device,
+ const gchar *profile);
+const gchar * gvc_mixer_ui_device_get_user_preferred_profile (GvcMixerUIDevice *device);
+const gchar * gvc_mixer_ui_device_get_top_priority_profile (GvcMixerUIDevice *device);
+GList * gvc_mixer_ui_device_get_profiles (GvcMixerUIDevice *device);
+GList * gvc_mixer_ui_device_get_supported_profiles (GvcMixerUIDevice *device);
+gboolean gvc_mixer_ui_device_should_profiles_be_hidden (GvcMixerUIDevice *device);
+void gvc_mixer_ui_device_set_profiles (GvcMixerUIDevice *device,
+ const GList *in_profiles);
+void gvc_mixer_ui_device_set_user_preferred_profile (GvcMixerUIDevice *device,
+ const gchar *profile);
+void gvc_mixer_ui_device_invalidate_stream (GvcMixerUIDevice *device);
+gboolean gvc_mixer_ui_device_has_ports (GvcMixerUIDevice *device);
+gboolean gvc_mixer_ui_device_is_output (GvcMixerUIDevice *device);
+
+G_END_DECLS
+
+#endif /* _GVC_MIXER_UI_DEVICE_H_ */
diff --git a/subprojects/gvc/gvc-pulseaudio-fake.h b/subprojects/gvc/gvc-pulseaudio-fake.h
new file mode 100644
index 0000000..92a41b6
--- /dev/null
+++ b/subprojects/gvc/gvc-pulseaudio-fake.h
@@ -0,0 +1,30 @@
+/* -*- 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_PULSEAUDIO_FAKE_H
+#define __GVC_PULSEAUDIO_FAKE_H
+
+#ifndef PA_API_VERSION
+#define pa_channel_position_t int
+#define pa_volume_t guint32
+#define pa_context gpointer
+#endif /* PA_API_VERSION */
+
+#endif /* __GVC_PULSEAUDIO_FAKE_H */
diff --git a/subprojects/gvc/libgnome-volume-control.doap b/subprojects/gvc/libgnome-volume-control.doap
new file mode 100644
index 0000000..2fcc8e1
--- /dev/null
+++ b/subprojects/gvc/libgnome-volume-control.doap
@@ -0,0 +1,32 @@
+<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
+ xmlns:gnome="http://api.gnome.org/doap-extensions#"
+ xmlns="http://usefulinc.com/ns/doap#">
+
+ <name xml:lang="en">libgnome-volume-control</name>
+ <shortdesc xml:lang="en">GObject layer for PulseAudio</shortdesc>
+ <description>
+ This library contains code to access PulseAudio using a GObject
+ based library, shared between gnome-control-center, gnome-settings-daemon
+ and gnome-shell. It is not API stable, and it is meant to be used
+ as a submodule.
+ </description>
+
+ <!-- <category rdf:resource="http://api.gnome.org/doap-extensions#desktop" /> -->
+
+ <maintainer>
+ <foaf:Person>
+ <foaf:name>Giovanni Campagna</foaf:name>
+ <foaf:mbox rdf:resource="mailto:scampa.giovanni@gmail.com" />
+ <gnome:userid>gcampagna</gnome:userid>
+ </foaf:Person>
+ </maintainer>
+ <maintainer>
+ <foaf:Person>
+ <foaf:name>Bastien Nocera</foaf:name>
+ <foaf:mbox rdf:resource="mailto:hadess@hadess.net" />
+ <gnome:userid>hadess</gnome:userid>
+ </foaf:Person>
+ </maintainer>
+</Project>
diff --git a/subprojects/gvc/meson.build b/subprojects/gvc/meson.build
new file mode 100644
index 0000000..a1a2af5
--- /dev/null
+++ b/subprojects/gvc/meson.build
@@ -0,0 +1,137 @@
+project('gvc', 'c',
+ meson_version: '>= 0.42.0',
+ default_options: ['static=true']
+)
+
+assert(meson.is_subproject(), 'This project is only intended to be used as a subproject!')
+
+gnome = import('gnome')
+
+pkglibdir = get_option('pkglibdir')
+pkgdatadir = get_option('pkgdatadir')
+
+cdata = configuration_data()
+cdata.set_quoted('GETTEXT_PACKAGE', get_option('package_name'))
+cdata.set_quoted('PACKAGE_VERSION', get_option('package_version'))
+
+libgvc_gir_headers = [
+ 'gvc-channel-map.h',
+ 'gvc-mixer-card.h',
+ 'gvc-mixer-control.h',
+ 'gvc-mixer-event-role.h',
+ 'gvc-mixer-sink.h',
+ 'gvc-mixer-sink-input.h',
+ 'gvc-mixer-source.h',
+ 'gvc-mixer-source-output.h',
+ 'gvc-mixer-stream.h',
+ 'gvc-mixer-ui-device.h'
+]
+
+libgvc_enums = gnome.mkenums_simple('gvc-enum-types',
+ sources: libgvc_gir_headers
+)
+
+libgvc_gir_sources = [
+ 'gvc-channel-map.c',
+ 'gvc-mixer-card.c',
+ 'gvc-mixer-control.c',
+ 'gvc-mixer-event-role.c',
+ 'gvc-mixer-sink.c',
+ 'gvc-mixer-sink-input.c',
+ 'gvc-mixer-source.c',
+ 'gvc-mixer-source-output.c',
+ 'gvc-mixer-stream.c',
+ 'gvc-mixer-ui-device.c'
+]
+
+libgvc_no_gir_sources = [
+ 'gvc-mixer-card-private.h',
+ 'gvc-mixer-stream-private.h',
+ 'gvc-channel-map-private.h',
+ 'gvc-mixer-control-private.h',
+ 'gvc-pulseaudio-fake.h'
+]
+
+libgvc_deps = [
+ dependency('gio-2.0'),
+ dependency('gobject-2.0'),
+ dependency('libpulse', version: '>= 12.99.3'),
+ dependency('libpulse-mainloop-glib')
+]
+
+enable_alsa = get_option('alsa')
+if enable_alsa
+ libgvc_deps += dependency('alsa')
+endif
+cdata.set('HAVE_ALSA', enable_alsa)
+
+enable_static = get_option('static')
+enable_introspection = get_option('introspection')
+
+assert(not enable_static or not enable_introspection, 'Currently meson requires a shared library for building girs.')
+assert(enable_static or pkglibdir != '', 'Installing shared library, but pkglibdir is unset!')
+
+c_args = ['-DG_LOG_DOMAIN="Gvc"']
+
+if enable_introspection
+ c_args += '-DWITH_INTROSPECTION'
+endif
+
+if enable_static
+ libgvc_static = static_library('gvc',
+ sources: libgvc_gir_sources + libgvc_no_gir_sources + libgvc_enums,
+ dependencies: libgvc_deps,
+ c_args: c_args
+ )
+
+ libgvc = libgvc_static
+else
+ if pkglibdir == ''
+ error('Installing shared library, but pkglibdir is unset!')
+ endif
+
+ libgvc_shared = shared_library('gvc',
+ sources: libgvc_gir_sources + libgvc_no_gir_sources + libgvc_enums,
+ dependencies: libgvc_deps,
+ c_args: c_args,
+ install_dir: pkglibdir,
+ install: true
+ )
+
+ libgvc = libgvc_shared
+endif
+
+if enable_introspection
+ assert(pkgdatadir != '', 'Installing introspection, but pkgdatadir is unset!')
+
+ libgvc_gir = gnome.generate_gir(libgvc,
+ sources: libgvc_gir_sources + libgvc_gir_headers + libgvc_enums,
+ nsversion: '1.0',
+ namespace: 'Gvc',
+ includes: ['Gio-2.0', 'GObject-2.0'],
+ extra_args: ['-DWITH_INTROSPECTION', '--quiet'],
+ install_dir_gir: pkgdatadir,
+ install_dir_typelib: pkglibdir,
+ install: true
+ )
+endif
+
+if enable_alsa
+ executable('test-audio-device-selection',
+ sources: 'test-audio-device-selection.c',
+ link_with: libgvc,
+ dependencies: libgvc_deps,
+ c_args: c_args
+ )
+endif
+
+libgvc_dep = declare_dependency(
+ link_with: libgvc,
+ include_directories: include_directories('.'),
+ dependencies: libgvc_deps
+)
+
+configure_file(
+ output: 'config.h',
+ configuration: cdata
+)
diff --git a/subprojects/gvc/meson_options.txt b/subprojects/gvc/meson_options.txt
new file mode 100644
index 0000000..38513e3
--- /dev/null
+++ b/subprojects/gvc/meson_options.txt
@@ -0,0 +1,41 @@
+option('package_name',
+ type: 'string',
+ value: '',
+ description: 'The value for the GETTEXT_PACKAGE define.'
+)
+
+option('package_version',
+ type: 'string',
+ value: '',
+ description: 'The value for the PACKAGE_VERSION define.'
+)
+
+option('pkglibdir',
+ type: 'string',
+ value: '',
+ description: 'The private directory the shared library/typelib will be installed into.'
+)
+
+option('pkgdatadir',
+ type: 'string',
+ value: '',
+ description: 'The private directory the gir file will be installed into.'
+)
+
+option('alsa',
+ type: 'boolean',
+ value: true,
+ description: 'Build ALSA support.'
+)
+
+option('static',
+ type: 'boolean',
+ value: false,
+ description: 'Build as a static library.'
+)
+
+option('introspection',
+ type: 'boolean',
+ value: false,
+ description: 'Build gobject-introspection support'
+)
diff --git a/subprojects/gvc/test-audio-device-selection.c b/subprojects/gvc/test-audio-device-selection.c
new file mode 100644
index 0000000..8195f9d
--- /dev/null
+++ b/subprojects/gvc/test-audio-device-selection.c
@@ -0,0 +1,84 @@
+
+#include <stdio.h>
+#include <locale.h>
+#include <pulse/pulseaudio.h>
+#include "gvc-mixer-control.h"
+
+#define MAX_ATTEMPTS 3
+
+typedef struct {
+ GvcHeadsetPortChoice choice;
+ const char *name;
+} AudioSelectionChoice;
+
+static AudioSelectionChoice audio_selection_choices[] = {
+ { GVC_HEADSET_PORT_CHOICE_HEADPHONES, "headphones" },
+ { GVC_HEADSET_PORT_CHOICE_HEADSET, "headset" },
+ { GVC_HEADSET_PORT_CHOICE_MIC, "microphone" },
+};
+
+static void
+audio_selection_needed (GvcMixerControl *volume,
+ guint id,
+ gboolean show_dialog,
+ GvcHeadsetPortChoice choices,
+ gpointer user_data)
+{
+ const char *args[G_N_ELEMENTS (audio_selection_choices) + 1];
+ guint i, n;
+ int response = -1;
+
+ if (!show_dialog) {
+ g_print ("--- Audio selection not needed anymore for id %d\n", id);
+ return;
+ }
+
+ n = 0;
+ for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) {
+ if (choices & audio_selection_choices[i].choice)
+ args[n++] = audio_selection_choices[i].name;
+ }
+ args[n] = NULL;
+
+ g_print ("+++ Audio selection needed for id %d\n", id);
+ g_print (" Choices are:\n");
+ for (i = 0; args[i] != NULL; i++)
+ g_print (" %d. %s\n", i + 1, args[i]);
+
+ for (i = 0; response < 0 && i < MAX_ATTEMPTS; i++) {
+ int res;
+
+ g_print ("What is your choice?\n");
+ if (scanf ("%d", &res) == 1 &&
+ res > 0 &&
+ res < (int) g_strv_length ((char **) args)) {
+ response = res;
+ break;
+ }
+ }
+
+ gvc_mixer_control_set_headset_port (volume,
+ id,
+ audio_selection_choices[response - 1].choice);
+}
+
+int main (int argc, char **argv)
+{
+ GMainLoop *loop;
+ GvcMixerControl *volume;
+
+ setlocale (LC_ALL, "");
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ volume = gvc_mixer_control_new ("GNOME Volume Control test");
+ g_signal_connect (volume,
+ "audio-device-selection-needed",
+ G_CALLBACK (audio_selection_needed),
+ NULL);
+ gvc_mixer_control_open (volume);
+
+ g_main_loop_run (loop);
+
+ return 0;
+}
diff --git a/subprojects/libadwaita.wrap b/subprojects/libadwaita.wrap
new file mode 100644
index 0000000..a0c0609
--- /dev/null
+++ b/subprojects/libadwaita.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://gitlab.gnome.org/GNOME/libadwaita.git
+revision = main