mirror of
https://gitlab.gnome.org/GNOME/libgnome-volume-control.git
synced 2024-11-23 01:10:40 -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/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"
|
||||
@ -97,6 +101,13 @@ struct GvcMixerControlPrivate
|
||||
* 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;
|
||||
#endif /* HAVE_ALSA */
|
||||
|
||||
GvcMixerControlState state;
|
||||
};
|
||||
|
||||
@ -115,6 +126,7 @@ enum {
|
||||
INPUT_ADDED,
|
||||
OUTPUT_REMOVED,
|
||||
INPUT_REMOVED,
|
||||
AUDIO_DEVICE_SELECTION_NEEDED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
@ -2053,6 +2065,332 @@ create_ui_device_from_card (GvcMixerControl *control,
|
||||
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')
|
||||
* 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),
|
||||
signals[CARD_ADDED],
|
||||
0,
|
||||
@ -3242,6 +3585,14 @@ gvc_mixer_control_class_init (GvcMixerControlClass *klass)
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__UINT,
|
||||
G_TYPE_NONE, 1, G_TYPE_UINT);
|
||||
signals [AUDIO_DEVICE_SELECTION_NEEDED] =
|
||||
g_signal_new ("audio-device-selection-needed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_generic,
|
||||
G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_UINT);
|
||||
signals [CARD_ADDED] =
|
||||
g_signal_new ("card-added",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
@ -3348,6 +3699,10 @@ gvc_mixer_control_init (GvcMixerControl *control)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,14 @@ typedef enum
|
||||
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))
|
||||
@ -83,6 +91,11 @@ typedef struct
|
||||
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);
|
||||
@ -131,6 +144,10 @@ gboolean gvc_mixer_control_change_profile_on_selected_device (Gvc
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user