Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:AZhou:branches:GNOME:Next
gnome-shell
libgnome-volume-control-0.gitmodule.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File libgnome-volume-control-0.gitmodule.obscpio of Package gnome-shell
07070100000000000081A400000000000000000000000166437C9400000067000000000000000000000000000000000000002F00000000libgnome-volume-control-0.gitmodule/.gitignore.deps/ .libs/ .dirstamp Makefile.in Makefile *.la *.lo *.o *.gir *.typelib test-audio-device-selection 07070100000001000041ED00000000000000000000000266437C9400000000000000000000000000000000000000000000002F00000000libgnome-volume-control-0.gitmodule/.gitlab-ci07070100000002000081A400000000000000000000000166437C9400000149000000000000000000000000000000000000003300000000libgnome-volume-control-0.gitmodule/.gitlab-ci.ymlstages: - test build-fedora: image: fedora:latest stage: test before_script: - dnf install -y redhat-rpm-config gcc clang meson pulseaudio-libs-devel alsa-lib-devel gtk3-devel script: - cd .gitlab-ci - meson _build - ninja -C _build - rm -rf _build - CC=clang meson _build - ninja -C _build 07070100000003000081A400000000000000000000000166437C9400000246000000000000000000000000000000000000003B00000000libgnome-volume-control-0.gitmodule/.gitlab-ci/meson.buildproject('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' ] ) 07070100000004000041ED00000000000000000000000266437C9400000000000000000000000000000000000000000000003B00000000libgnome-volume-control-0.gitmodule/.gitlab-ci/subprojects070701000000050000A1FF00000000000000000000000166437C9400000006000000000000000000000000000000000000003F00000000libgnome-volume-control-0.gitmodule/.gitlab-ci/subprojects/gvc../../07070100000006000081A400000000000000000000000166437C9400000218000000000000000000000000000000000000002E00000000libgnome-volume-control-0.gitmodule/README.md# 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) 07070100000007000081A400000000000000000000000166437C940000062E000000000000000000000000000000000000003E00000000libgnome-volume-control-0.gitmodule/gvc-channel-map-private.h/* -*- 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 */ 07070100000008000081A400000000000000000000000166437C9400001C10000000000000000000000000000000000000003600000000libgnome-volume-control-0.gitmodule/gvc-channel-map.c/* -*- 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, NULL, 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); } 07070100000009000081A400000000000000000000000166437C9400000B78000000000000000000000000000000000000003600000000libgnome-volume-control-0.gitmodule/gvc-channel-map.h/* -*- 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 */ 0707010000000A000081A400000000000000000000000166437C94000005DF000000000000000000000000000000000000003D00000000libgnome-volume-control-0.gitmodule/gvc-mixer-card-private.h/* -*- 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); void gvc_mixer_card_add_port (GvcMixerCard *card, GvcMixerCardPort *port); void gvc_mixer_card_remove_port (GvcMixerCard *card, GvcMixerCardPort *port); G_END_DECLS #endif /* __GVC_MIXER_CARD_PRIVATE_H */ 0707010000000B000081A400000000000000000000000166437C9400004A30000000000000000000000000000000000000003500000000libgnome-volume-control-0.gitmodule/gvc-mixer-card.c/* -*- 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, N_PROPS }; static GParamSpec *obj_props[N_PROPS] = { NULL, }; 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_by_pspec (G_OBJECT (card), obj_props[PROP_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_by_pspec (G_OBJECT (card), obj_props[PROP_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); if (g_strcmp0 (card->priv->profile, profile) == 0) return TRUE; 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_by_pspec (G_OBJECT (card), obj_props[PROP_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; } static void free_profile (GvcMixerCardProfile *p) { g_free (p->profile); g_free (p->human_profile); g_free (p->status); g_free (p); } /** * 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_list_free_full (card->priv->profiles, (GDestroyNotify) free_profile); 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; } void gvc_mixer_card_add_port (GvcMixerCard *card, GvcMixerCardPort *port) { card->priv->ports = g_list_prepend (card->priv->ports, port); } void gvc_mixer_card_remove_port (GvcMixerCard *card, GvcMixerCardPort *port) { card->priv->ports = g_list_remove (card->priv->ports, port); free_port (port); } 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; obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[PROP_NAME] = g_param_spec_string ("name", "Name", "Name to display for this card", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[PROP_PROFILE] = g_param_spec_string ("profile", "Profile", "Name of current profile for this card", NULL, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[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|G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, N_PROPS, obj_props); } 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 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); } 0707010000000C000081A400000000000000000000000166437C9400000FFC000000000000000000000000000000000000003500000000libgnome-volume-control-0.gitmodule/gvc-mixer-card.h/* -*- 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 */ 0707010000000D000081A400000000000000000000000166437C94000004A4000000000000000000000000000000000000004000000000libgnome-volume-control-0.gitmodule/gvc-mixer-control-private.h/* -*- 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 */ 0707010000000E000081A400000000000000000000000166437C9400023BF2000000000000000000000000000000000000003800000000libgnome-volume-control-0.gitmodule/gvc-mixer-control.c/* -*- 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, N_PROPS }; static GParamSpec *obj_props[N_PROPS] = { NULL, }; 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; } 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; } 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) { if (!control->priv->default_source_is_set) return; g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control), on_default_source_port_notify, control); 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; 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); 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); 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; g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control), on_default_sink_port_notify, control); 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; guint 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", 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 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); stream_ports = gvc_mixer_stream_get_ports (stream); if (stream_ports == NULL) { GvcMixerUIDevice *device; GObject *object; object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE, "stream-id", 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, "icon-name", gvc_mixer_stream_get_icon_name (stream), 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; 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, "application-x-executable"); 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, const GList *card_profiles) { guint i; GList *supported_profiles = NULL; const 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 update_ui_device_on_port_added (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 ("update_ui_device_on_port_added, direction %u, description '%s', origin '%s', port available %i", direction, port->human_port, gvc_mixer_card_get_name (card), available); } static void update_ui_device_on_port_changed (GvcMixerControl *control, GvcMixerCardPort *card_port, pa_card_port_info *new_port_info, GvcMixerCard *card) { 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) { gboolean was_available; gboolean is_available; const GList *card_profiles = gvc_mixer_card_get_profiles (card); was_available = card_port->available != PA_PORT_AVAILABLE_NO; is_available = new_port_info->available != PA_PORT_AVAILABLE_NO; g_debug ("Found the relevant device %s, update its port availability flag to %i, is_output %i", device_port_name, is_available, is_output); card_port->available = new_port_info->available; g_list_free (card_port->profiles); card_port->profiles = determine_profiles_for_port (new_port_info, card_profiles); gvc_mixer_ui_device_set_profiles (device, card_port->profiles); if (is_available != was_available) { g_object_set (G_OBJECT (device), "port-available", is_available, NULL); g_signal_emit (G_OBJECT (control), is_output ? signals[is_available ? OUTPUT_ADDED : OUTPUT_REMOVED] : signals[is_available ? INPUT_ADDED : INPUT_REMOVED], 0, gvc_mixer_ui_device_get_id (device)); } } g_free (device_port_name); } g_list_free (devices); } static void maybe_remove_ui_device (GvcMixerControl *control, GvcMixerUIDevice *device) { /* We add UIDevices for ports or for streams, so remove them if the device now * has neither. */ if (gvc_mixer_ui_device_get_stream_id (device) == GVC_MIXER_UI_DEVICE_INVALID && !gvc_mixer_ui_device_has_ports (device)) { gboolean is_output = gvc_mixer_ui_device_is_output (device); g_debug ("Removing UIDevice %s", gvc_mixer_ui_device_get_description (device)); g_hash_table_remove (is_output ? control->priv->ui_outputs : control->priv->ui_inputs, GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device))); } } static void update_ui_device_on_port_removed (GvcMixerControl *control, GvcMixerCardPort *card_port, GvcMixerCard *card) { GList *d; GList *devices; 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) { GvcMixerUIDevice *device = d->data; GvcMixerCard *device_card; gchar *device_port_name; 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_object_set (G_OBJECT (device), "card", NULL, "port-name", NULL, NULL); g_signal_emit (G_OBJECT (control), signals[is_output ? OUTPUT_REMOVED : INPUT_REMOVED], 0, gvc_mixer_ui_device_get_id (device)); maybe_remove_ui_device (control, device); } g_free (device_port_name); } g_list_free (devices); } #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. * - 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 *m = NULL; GvcMixerCard *card; gboolean is_new = FALSE; GList *profile_list = NULL; GList *old_ports; #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) { card = gvc_mixer_card_new (control->priv->pa_context, info->index); is_new = TRUE; } 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); } gvc_mixer_card_set_profiles (card, profile_list); 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); } old_ports = g_list_copy ((GList *)gvc_mixer_card_get_ports (card)); for (m = old_ports; m; m = m->next) { GvcMixerCardPort *card_port = m->data; gboolean found = FALSE; for (i = 0; i < info->n_ports; i++) { pa_card_port_info *port = info->ports[i]; if (g_strcmp0 (card_port->port, port->name) == 0) found = TRUE; } if (!found) { update_ui_device_on_port_removed (control, card_port, card); gvc_mixer_card_remove_port (card, card_port); } } g_clear_pointer (&old_ports, g_list_free); for (i = 0; i < info->n_ports; i++) { pa_card_port_info *port = info->ports[i]; gboolean found = FALSE; for (m = gvc_mixer_card_get_ports (card); m; m = m->next) { GvcMixerCardPort *card_port = m->data; if (g_strcmp0 (card_port->port, port->name) == 0) { found = TRUE; update_ui_device_on_port_changed (control, card_port, port, card); } } if (!found) { GvcMixerCardPort *card_port; card_port = g_new0 (GvcMixerCardPort, 1); card_port->port = g_strdup (port->name); card_port->human_port = g_strdup (port->description); card_port->priority = port->priority; card_port->available = port->available; card_port->direction = port->direction; card_port->icon_name = g_strdup (pa_proplist_gets (port->proplist, "device.icon_name")); card_port->profiles = determine_profiles_for_port (port, profile_list); gvc_mixer_card_add_port (card, card_port); update_ui_device_on_port_added (control, card_port, card); } } #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)); } /* 0 channels here means there is no valid volume in * pa_ext_stream_restore_info() and in the saved stream entry of * module-stream-restore in PulseAudio. */ if (info->volume.channels == 0) max_volume = PA_VOLUME_NORM; else max_volume = pa_cvolume_max (&info->volume); gvc_mixer_stream_set_name (stream, _("System Sounds")); gvc_mixer_stream_set_icon_name (stream, "audio-x-generic"); 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 (card == NULL) continue; 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_items (GvcMixerControl *control, GHashTable *hash_table, void (*remove_item)(GvcMixerControl *control, guint index)) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, hash_table); while (g_hash_table_iter_next (&iter, &key, &value)) { if (remove_item) { remove_item (control, GPOINTER_TO_UINT (key)); g_hash_table_remove (hash_table, key); g_hash_table_iter_init (&iter, hash_table); } else { g_hash_table_iter_remove (&iter); } } } static gboolean idle_reconnect (gpointer data) { GvcMixerControl *control = GVC_MIXER_CONTROL (data); g_return_val_if_fail (control, FALSE); g_debug ("Reconnect: clean up all objects"); remove_all_items (control, control->priv->sinks, remove_sink); remove_all_items (control, control->priv->sources, remove_source); remove_all_items (control, control->priv->sink_inputs, remove_sink_input); remove_all_items (control, control->priv->source_outputs, remove_source_output); remove_all_items (control, control->priv->cards, remove_card); remove_all_items (control, control->priv->ui_inputs, NULL); remove_all_items (control, control->priv->ui_outputs, NULL); remove_all_items (control, control->priv->clients, remove_client); g_debug ("Reconnect: make new connection"); 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); } 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_by_pspec (G_OBJECT (self), obj_props[PROP_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; obj_props[PROP_NAME] = g_param_spec_string ("name", "Name", "Name to display for this mixer control", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, obj_props); 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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; } 0707010000000F000081A400000000000000000000000166437C94000020FF000000000000000000000000000000000000003800000000libgnome-volume-control-0.gitmodule/gvc-mixer-control.h/* -*- 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 */ 07070100000010000081A400000000000000000000000166437C9400001CC8000000000000000000000000000000000000003B00000000libgnome-volume-control-0.gitmodule/gvc-mixer-event-role.c/* -*- 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, N_PROPS }; static GParamSpec *obj_props[N_PROPS] = { NULL, }; 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_by_pspec (G_OBJECT (role), obj_props[PROP_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; obj_props[PROP_DEVICE] = g_param_spec_string ("device", "Device", "Device", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, obj_props); } 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); } 07070100000011000081A400000000000000000000000166437C94000008DC000000000000000000000000000000000000003B00000000libgnome-volume-control-0.gitmodule/gvc-mixer-event-role.h/* -*- 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 */ 07070100000012000081A400000000000000000000000166437C9400001257000000000000000000000000000000000000003B00000000libgnome-volume-control-0.gitmodule/gvc-mixer-sink-input.c/* -*- 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); } 07070100000013000081A400000000000000000000000166437C94000008DB000000000000000000000000000000000000003B00000000libgnome-volume-control-0.gitmodule/gvc-mixer-sink-input.h/* -*- 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 */ 07070100000014000081A400000000000000000000000166437C94000015A7000000000000000000000000000000000000003500000000libgnome-volume-control-0.gitmodule/gvc-mixer-sink.c/* -*- 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); } 07070100000015000081A400000000000000000000000166437C9400000854000000000000000000000000000000000000003500000000libgnome-volume-control-0.gitmodule/gvc-mixer-sink.h/* -*- 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 */ 07070100000016000081A400000000000000000000000166437C94000012D5000000000000000000000000000000000000003E00000000libgnome-volume-control-0.gitmodule/gvc-mixer-source-output.c/* -*- 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); } 07070100000017000081A400000000000000000000000166437C940000092F000000000000000000000000000000000000003E00000000libgnome-volume-control-0.gitmodule/gvc-mixer-source-output.h/* -*- 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 */ 07070100000018000081A400000000000000000000000166437C9400001611000000000000000000000000000000000000003700000000libgnome-volume-control-0.gitmodule/gvc-mixer-source.c/* -*- 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); } 07070100000019000081A400000000000000000000000166437C9400000884000000000000000000000000000000000000003700000000libgnome-volume-control-0.gitmodule/gvc-mixer-source.h/* -*- 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 */ 0707010000001A000081A400000000000000000000000166437C9400000460000000000000000000000000000000000000003F00000000libgnome-volume-control-0.gitmodule/gvc-mixer-stream-private.h/* -*- 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 */ 0707010000001B000081A400000000000000000000000166437C9400008FAB000000000000000000000000000000000000003700000000libgnome-volume-control-0.gitmodule/gvc-mixer-stream.c/* -*- 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, N_PROPS }; static GParamSpec *obj_props[N_PROPS] = { NULL, }; 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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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_by_pspec (G_OBJECT (stream), obj_props[PROP_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; obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[PROP_VOLUME] = g_param_spec_ulong ("volume", "Volume", "The volume for this stream", 0, G_MAXULONG, 0, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[PROP_NAME] = g_param_spec_string ("name", "Name", "Name to display for this stream", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_STATIC_STRINGS); obj_props[PROP_DESCRIPTION] = g_param_spec_string ("description", "Description", "Description to display for this stream", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_STATIC_STRINGS); obj_props[PROP_APPLICATION_ID] = g_param_spec_string ("application-id", "Application identifier", "Application identifier for this stream", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[PROP_IS_MUTED] = g_param_spec_boolean ("is-muted", "is muted", "Whether stream is muted", FALSE, G_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[PROP_PORT] = g_param_spec_string ("port", "Port", "The name of the current port for this stream", NULL, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); obj_props[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|G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, N_PROPS, obj_props); } 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); } 0707010000001C000081A400000000000000000000000166437C9400001DDF000000000000000000000000000000000000003700000000libgnome-volume-control-0.gitmodule/gvc-mixer-stream.h/* -*- 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 */ 0707010000001D000081A400000000000000000000000166437C9400006C11000000000000000000000000000000000000003A00000000libgnome-volume-control-0.gitmodule/gvc-mixer-ui-device.c/* -*- 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, N_PROPS }; static GParamSpec *obj_props[N_PROPS] = { NULL, }; 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); g_debug ("gvc-mixer-output-set-property - device type: %s", self->priv->type == UIDeviceInput ? "input" : "output"); break; case PROP_PORT_AVAILABLE: g_debug ("gvc-mixer-output-set-property - old port available %i, value passed in %i", self->priv->port_available, g_value_get_boolean (value)); 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)); g_debug ("gvc-mixer-output-set-property - icon name: %s", self->priv->icon_name); 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); 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; obj_props[PROP_DESC_LINE_1] = g_param_spec_string ("description", "Description construct prop", "Set first line description", "no-name-set", G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[PROP_DESC_LINE_2] = g_param_spec_string ("origin", "origin construct prop", "Set second line description name", "no-name-set", G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[PROP_CARD] = g_param_spec_pointer ("card", "Card from pulse", "Set/Get card", G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[PROP_PORT_NAME] = g_param_spec_string ("port-name", "port-name construct prop", "Set port-name", NULL, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[PROP_STREAM_ID] = 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_PARAM_STATIC_STRINGS); obj_props[PROP_UI_DEVICE_TYPE] = g_param_spec_uint ("type", "ui-device type", "determine whether its an input and output", 0, 1, 0, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[PROP_PORT_AVAILABLE] = g_param_spec_boolean ("port-available", "available", "determine whether this port is available", FALSE, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); obj_props[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_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, obj_props); } /* 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)); g_clear_pointer (&device->priv->supported_profiles, g_list_free); g_clear_pointer (&device->priv->profiles, g_list_free); 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_by_pspec (G_OBJECT (device), obj_props[PROP_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); } 0707010000001E000081A400000000000000000000000166437C9400001121000000000000000000000000000000000000003A00000000libgnome-volume-control-0.gitmodule/gvc-mixer-ui-device.h/* -*- 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_ */ 0707010000001F000081A400000000000000000000000166437C940000043B000000000000000000000000000000000000003A00000000libgnome-volume-control-0.gitmodule/gvc-pulseaudio-fake.h/* -*- 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 */ 07070100000020000081A400000000000000000000000166437C94000004A6000000000000000000000000000000000000004100000000libgnome-volume-control-0.gitmodule/libgnome-volume-control.doap<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>Emmanuele Bassi</foaf:name> <foaf:mbox rdf:resource="mailto:ebassi@gnome.org"/> <gnome:userid>ebassi</gnome:userid> </foaf:Person> </maintainer> <author> <foaf:Person> <foaf:name>Bastien Nocera</foaf:name> <foaf:mbox rdf:resource="mailto:hadess@hadess.net" /> <gnome:userid>hadess</gnome:userid> </foaf:Person> </author> </Project> 07070100000021000081A400000000000000000000000166437C9400000D19000000000000000000000000000000000000003000000000libgnome-volume-control-0.gitmodule/meson.buildproject('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 ) 07070100000022000081A400000000000000000000000166437C940000032C000000000000000000000000000000000000003600000000libgnome-volume-control-0.gitmodule/meson_options.txtoption('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' ) 07070100000023000081A400000000000000000000000166437C94000007BB000000000000000000000000000000000000004200000000libgnome-volume-control-0.gitmodule/test-audio-device-selection.c #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; } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!636 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor