mirror of
https://gitlab.gnome.org/GNOME/libgnome-volume-control.git
synced 2024-11-21 00:10:41 -05:00
gvc: Add "what did you plug in" support API
Add "audio-device-selection-needed" which will be emitted when a headphones, headset or microphone is plugged into a jack socket that cannot detect which type it was. Once the user of libgnome-volume-control has asked the user which type of device this was, they can call gvc_mixer_control_set_headset_port() to switch the ports for that configuration. Note that gvc_mixer_control_set_headset_port() supports passing the card ID, but the detection code only supports a single such device. When we find hardware that can support > 1 such device, we can test and implement support without breaking the API. Based on the original code by David Henningsson <david.henningsson@canonical.com> for the unity-settings-daemon https://bugzilla.gnome.org/show_bug.cgi?id=755062
This commit is contained in:
parent
d4eda71c49
commit
f3f6812eb9
@ -34,6 +34,10 @@
|
|||||||
#include <pulse/glib-mainloop.h>
|
#include <pulse/glib-mainloop.h>
|
||||||
#include <pulse/ext-stream-restore.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-control.h"
|
||||||
#include "gvc-mixer-sink.h"
|
#include "gvc-mixer-sink.h"
|
||||||
#include "gvc-mixer-source.h"
|
#include "gvc-mixer-source.h"
|
||||||
@ -97,6 +101,13 @@ struct GvcMixerControlPrivate
|
|||||||
* device the user wishes to use. */
|
* device the user wishes to use. */
|
||||||
guint profile_swapping_device_id;
|
guint profile_swapping_device_id;
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
int headset_card;
|
||||||
|
gboolean has_headsetmic;
|
||||||
|
gboolean has_headphonemic;
|
||||||
|
gboolean headset_plugged_in;
|
||||||
|
#endif /* HAVE_ALSA */
|
||||||
|
|
||||||
GvcMixerControlState state;
|
GvcMixerControlState state;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -115,6 +126,7 @@ enum {
|
|||||||
INPUT_ADDED,
|
INPUT_ADDED,
|
||||||
OUTPUT_REMOVED,
|
OUTPUT_REMOVED,
|
||||||
INPUT_REMOVED,
|
INPUT_REMOVED,
|
||||||
|
AUDIO_DEVICE_SELECTION_NEEDED,
|
||||||
LAST_SIGNAL
|
LAST_SIGNAL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2053,6 +2065,332 @@ create_ui_device_from_card (GvcMixerControl *control,
|
|||||||
g_object_ref (out));
|
g_object_ref (out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
typedef struct {
|
||||||
|
char *port_name_to_set;
|
||||||
|
int 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;
|
||||||
|
int j;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
if (eol) {
|
||||||
|
port_status_data_free (data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i->card != data->headset_card)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (i->active_port &&
|
||||||
|
strcmp (i->active_port->name, s) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s = data->port_name_to_set;
|
||||||
|
|
||||||
|
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);
|
||||||
|
port_status_data_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
source_info_cb (pa_context *c,
|
||||||
|
const pa_source_info *i,
|
||||||
|
int eol,
|
||||||
|
void *userdata)
|
||||||
|
{
|
||||||
|
PortStatusData *data = userdata;
|
||||||
|
pa_operation *o;
|
||||||
|
int j;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
if (eol) {
|
||||||
|
port_status_data_free (data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i->card != data->headset_card)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (i->active_port && strcmp (i->active_port->name, s) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s = data->port_name_to_set;
|
||||||
|
|
||||||
|
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);
|
||||||
|
port_status_data_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 */
|
||||||
|
|
||||||
|
void
|
||||||
|
gvc_mixer_control_set_headset_port (GvcMixerControl *control,
|
||||||
|
guint id,
|
||||||
|
GvcHeadsetPortChoice choice)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
switch (choice) {
|
||||||
|
case GVC_HEADSET_PORT_CHOICE_HEADPHONES:
|
||||||
|
gvc_mixer_control_set_port_status_for_headset (control, id, "analog-output-headphones", TRUE);
|
||||||
|
gvc_mixer_control_set_port_status_for_headset (control, id, "analog-input-internal-mic", FALSE);
|
||||||
|
break;
|
||||||
|
case GVC_HEADSET_PORT_CHOICE_HEADSET:
|
||||||
|
gvc_mixer_control_set_port_status_for_headset (control, id, "analog-output-headphones", TRUE);
|
||||||
|
gvc_mixer_control_set_port_status_for_headset (control, id, "analog-input-headset-mic", FALSE);
|
||||||
|
break;
|
||||||
|
case GVC_HEADSET_PORT_CHOICE_MIC:
|
||||||
|
gvc_mixer_control_set_port_status_for_headset (control, id, "analog-output-speaker", TRUE);
|
||||||
|
gvc_mixer_control_set_port_status_for_headset (control, id, "analog-input-headphone-mic", FALSE);
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
} headset_ports;
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Check if we still need this with the changed PA port names
|
||||||
|
|
||||||
|
In PulseAudio 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static headset_ports *
|
||||||
|
get_headset_ports (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 (strcmp (p->name, "analog-output-headphones") == 0)
|
||||||
|
h->headphones = p;
|
||||||
|
else if (strcmp (p->name, "analog-input-headset-mic") == 0)
|
||||||
|
h->headsetmic = p;
|
||||||
|
else if (strcmp(p->name, "analog-input-headphone-mic") == 0)
|
||||||
|
h->headphonemic = p;
|
||||||
|
}
|
||||||
|
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 (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, hpmic;
|
||||||
|
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 (!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;
|
||||||
|
|
||||||
|
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')
|
* At this point we can determine all devices available to us (besides network 'ports')
|
||||||
* This is done by the following:
|
* This is done by the following:
|
||||||
@ -2175,6 +2513,11 @@ update_card (GvcMixerControl *control,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
check_audio_device_selection_needed (control, info);
|
||||||
|
#endif /* HAVE_ALSA */
|
||||||
|
|
||||||
g_signal_emit (G_OBJECT (control),
|
g_signal_emit (G_OBJECT (control),
|
||||||
signals[CARD_ADDED],
|
signals[CARD_ADDED],
|
||||||
0,
|
0,
|
||||||
@ -3242,6 +3585,14 @@ gvc_mixer_control_class_init (GvcMixerControlClass *klass)
|
|||||||
NULL, NULL,
|
NULL, NULL,
|
||||||
g_cclosure_marshal_VOID__UINT,
|
g_cclosure_marshal_VOID__UINT,
|
||||||
G_TYPE_NONE, 1, G_TYPE_UINT);
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
||||||
|
signals [AUDIO_DEVICE_SELECTION_NEEDED] =
|
||||||
|
g_signal_new ("audio-device-selection-needed",
|
||||||
|
G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST,
|
||||||
|
0,
|
||||||
|
NULL, NULL,
|
||||||
|
g_cclosure_marshal_generic,
|
||||||
|
G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_UINT);
|
||||||
signals [CARD_ADDED] =
|
signals [CARD_ADDED] =
|
||||||
g_signal_new ("card-added",
|
g_signal_new ("card-added",
|
||||||
G_TYPE_FROM_CLASS (klass),
|
G_TYPE_FROM_CLASS (klass),
|
||||||
@ -3348,6 +3699,10 @@ gvc_mixer_control_init (GvcMixerControl *control)
|
|||||||
|
|
||||||
control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
|
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;
|
control->priv->state = GVC_STATE_CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,14 @@ typedef enum
|
|||||||
GVC_STATE_FAILED
|
GVC_STATE_FAILED
|
||||||
} GvcMixerControlState;
|
} 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_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(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_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass))
|
||||||
@ -83,6 +91,11 @@ typedef struct
|
|||||||
guint id);
|
guint id);
|
||||||
void (*input_removed) (GvcMixerControl *control,
|
void (*input_removed) (GvcMixerControl *control,
|
||||||
guint id);
|
guint id);
|
||||||
|
void (*audio_device_selection_needed)
|
||||||
|
(GvcMixerControl *control,
|
||||||
|
guint id,
|
||||||
|
gboolean show_dialog,
|
||||||
|
GvcHeadsetPortChoice choices);
|
||||||
} GvcMixerControlClass;
|
} GvcMixerControlClass;
|
||||||
|
|
||||||
GType gvc_mixer_control_get_type (void);
|
GType gvc_mixer_control_get_type (void);
|
||||||
@ -131,6 +144,10 @@ gboolean gvc_mixer_control_change_profile_on_selected_device (Gvc
|
|||||||
GvcMixerUIDevice *device,
|
GvcMixerUIDevice *device,
|
||||||
const gchar* profile);
|
const gchar* profile);
|
||||||
|
|
||||||
|
void gvc_mixer_control_set_headset_port (GvcMixerControl *control,
|
||||||
|
guint id,
|
||||||
|
GvcHeadsetPortChoice choices);
|
||||||
|
|
||||||
GvcMixerControlState gvc_mixer_control_get_state (GvcMixerControl *control);
|
GvcMixerControlState gvc_mixer_control_get_state (GvcMixerControl *control);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
Loading…
Reference in New Issue
Block a user