summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/modules/alsa/alsa-mixer.c239
-rw-r--r--src/modules/alsa/alsa-mixer.h27
-rw-r--r--src/modules/alsa/alsa-sink.c7
-rw-r--r--src/modules/alsa/alsa-source.c7
-rw-r--r--src/modules/alsa/alsa-util.c125
-rw-r--r--src/modules/alsa/alsa-util.h4
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-dock-mic.conf3
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-front-mic.conf3
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-linein.conf3
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic.conf3
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-rear-mic.conf3
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-headphones.conf5
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf.common8
-rw-r--r--src/modules/alsa/module-alsa-card.c132
14 files changed, 464 insertions, 105 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 0e535503..1813ad9f 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -72,6 +72,7 @@ struct pa_alsa_fdlist {
struct pollfd *work_fds;
snd_mixer_t *mixer;
+ snd_hctl_t *hctl;
pa_mainloop_api *m;
pa_defer_event *defer;
@@ -92,7 +93,7 @@ static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_
pa_assert(a);
pa_assert(fdl);
- pa_assert(fdl->mixer);
+ pa_assert(fdl->mixer || fdl->hctl);
pa_assert(fdl->fds);
pa_assert(fdl->work_fds);
@@ -119,15 +120,24 @@ static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_
pa_assert(i != fdl->num_fds);
- if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
+ if (fdl->hctl)
+ err = snd_hctl_poll_descriptors_revents(fdl->hctl, fdl->work_fds, fdl->num_fds, &revents);
+ else
+ err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents);
+
+ if (err < 0) {
pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
return;
}
a->defer_enable(fdl->defer, 1);
- if (revents)
- snd_mixer_handle_events(fdl->mixer);
+ if (revents) {
+ if (fdl->hctl)
+ snd_hctl_handle_events(fdl->hctl);
+ else
+ snd_mixer_handle_events(fdl->mixer);
+ }
}
static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
@@ -138,11 +148,16 @@ static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
pa_assert(a);
pa_assert(fdl);
- pa_assert(fdl->mixer);
+ pa_assert(fdl->mixer || fdl->hctl);
a->defer_enable(fdl->defer, 0);
- if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) {
+ if (fdl->hctl)
+ n = snd_hctl_poll_descriptors_count(fdl->hctl);
+ else
+ n = snd_mixer_poll_descriptors_count(fdl->mixer);
+
+ if (n < 0) {
pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
return;
}
@@ -159,7 +174,12 @@ static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
- if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
+ if (fdl->hctl)
+ err = snd_hctl_poll_descriptors(fdl->hctl, fdl->work_fds, num_fds);
+ else
+ err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds);
+
+ if (err < 0) {
pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
return;
}
@@ -228,12 +248,15 @@ void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
pa_xfree(fdl);
}
-int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api *m) {
+/* We can listen to either a snd_hctl_t or a snd_mixer_t, but not both */
+int pa_alsa_fdlist_set_handle(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api *m) {
pa_assert(fdl);
- pa_assert(mixer_handle);
+ pa_assert(hctl_handle || mixer_handle);
+ pa_assert(!(hctl_handle && mixer_handle));
pa_assert(m);
pa_assert(!fdl->m);
+ fdl->hctl = hctl_handle;
fdl->mixer = mixer_handle;
fdl->m = m;
fdl->defer = m->defer_new(m, defer_cb, fdl);
@@ -355,81 +378,7 @@ int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer,
return 0;
}
-static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
- int err;
-
- pa_assert(mixer);
- pa_assert(dev);
-
- if ((err = snd_mixer_attach(mixer, dev)) < 0) {
- pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
- return -1;
- }
-
- if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
- pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
- return -1;
- }
-
- if ((err = snd_mixer_load(mixer)) < 0) {
- pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
- return -1;
- }
-
- pa_log_info("Successfully attached to mixer '%s'", dev);
- return 0;
-}
-
-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) {
- int err;
- snd_mixer_t *m;
- const char *dev;
- snd_pcm_info_t* info;
- snd_pcm_info_alloca(&info);
-
- pa_assert(pcm);
-
- if ((err = snd_mixer_open(&m, 0)) < 0) {
- pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
- return NULL;
- }
-
- /* First, try by name */
- if ((dev = snd_pcm_name(pcm)))
- if (prepare_mixer(m, dev) >= 0) {
- if (ctl_device)
- *ctl_device = pa_xstrdup(dev);
-
- return m;
- }
-
- /* Then, try by card index */
- if (snd_pcm_info(pcm, info) >= 0) {
- char *md;
- int card_idx;
-
- if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
-
- md = pa_sprintf_malloc("hw:%i", card_idx);
- if (!dev || !pa_streq(dev, md))
- if (prepare_mixer(m, md) >= 0) {
-
- if (ctl_device)
- *ctl_device = md;
- else
- pa_xfree(md);
-
- return m;
- }
-
- pa_xfree(md);
- }
- }
-
- snd_mixer_close(m);
- return NULL;
-}
static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
[PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
@@ -523,6 +472,14 @@ static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
pa_xfree(db_fix);
}
+static void jack_free(pa_alsa_jack *j) {
+ pa_assert(j);
+
+ pa_xfree(j->alsa_name);
+ pa_xfree(j->name);
+ pa_xfree(j);
+}
+
static void element_free(pa_alsa_element *e) {
pa_alsa_option *o;
pa_assert(e);
@@ -540,11 +497,17 @@ static void element_free(pa_alsa_element *e) {
}
void pa_alsa_path_free(pa_alsa_path *p) {
+ pa_alsa_jack *j;
pa_alsa_element *e;
pa_alsa_setting *s;
pa_assert(p);
+ while ((j = p->jacks)) {
+ PA_LLIST_REMOVE(pa_alsa_jack, p->jacks, j);
+ jack_free(j);
+ }
+
while ((e = p->elements)) {
PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
element_free(e);
@@ -1689,6 +1652,26 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
return 0;
}
+static int jack_probe(pa_alsa_jack *j, snd_hctl_t *h) {
+ pa_assert(h);
+ pa_assert(j);
+ pa_assert(j->path);
+
+ j->has_control = pa_alsa_find_jack(h, j->alsa_name) != NULL;
+
+ if (j->has_control) {
+ if (j->required_absent != PA_ALSA_REQUIRED_IGNORE)
+ return -1;
+ if (j->required_any != PA_ALSA_REQUIRED_IGNORE)
+ j->path->req_any_present = TRUE;
+ } else {
+ if (j->required != PA_ALSA_REQUIRED_IGNORE)
+ return -1;
+ }
+
+ return 0;
+}
+
static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) {
pa_alsa_element *e;
@@ -1726,6 +1709,32 @@ finish:
return e;
}
+static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
+ pa_alsa_jack *j;
+
+ if (!pa_startswith(section, "Jack "))
+ return NULL;
+ section += 5;
+
+ if (p->last_jack && pa_streq(p->last_jack->name, section))
+ return p->last_jack;
+
+ PA_LLIST_FOREACH(j, p->jacks)
+ if (pa_streq(j->name, section))
+ goto finish;
+
+ j = pa_xnew0(pa_alsa_jack, 1);
+ j->path = p;
+ j->name = pa_xstrdup(section);
+ j->alsa_name = pa_sprintf_malloc("%s Jack", section);
+ PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j);
+
+finish:
+ p->last_jack = j;
+ return j;
+}
+
+
static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
char *en;
const char *on;
@@ -1949,13 +1958,15 @@ static int element_parse_required(
pa_alsa_path *p = userdata;
pa_alsa_element *e;
pa_alsa_option *o;
+ pa_alsa_jack *j;
pa_alsa_required_t req;
pa_assert(p);
e = element_get(p, section, TRUE);
o = option_get(p, section);
- if (!e && !o) {
+ j = jack_get(p, section);
+ if (!e && !o && !j) {
pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section);
return -1;
}
@@ -1980,6 +1991,8 @@ static int element_parse_required(
e->required_absent = req;
if (o)
o->required_absent = req;
+ if (j)
+ j->required_absent = req;
}
else if (pa_streq(lvalue, "required-any")) {
if (e) {
@@ -1990,12 +2003,19 @@ static int element_parse_required(
o->required_any = req;
o->element->path->has_req_any = TRUE;
}
+ if (j) {
+ j->required_any = req;
+ j->path->has_req_any = TRUE;
+ }
+
}
else {
if (e)
e->required = req;
if (o)
o->required = req;
+ if (j)
+ j->required = req;
}
return 0;
@@ -2564,8 +2584,9 @@ static void path_create_settings(pa_alsa_path *p) {
element_create_settings(p->elements, NULL);
}
-int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
+int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, snd_hctl_t *hctl, pa_bool_t ignore_dB) {
pa_alsa_element *e;
+ pa_alsa_jack *j;
double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
pa_channel_position_t t;
pa_channel_position_mask_t path_volume_channels = 0;
@@ -2582,6 +2603,15 @@ int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
pa_log_debug("Probing path '%s'", p->name);
+ PA_LLIST_FOREACH(j, p->jacks) {
+ if (jack_probe(j, hctl) < 0) {
+ p->supported = FALSE;
+ pa_log_debug("Probe of jack '%s' failed.", j->alsa_name);
+ return -1;
+ }
+ pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found");
+ }
+
PA_LLIST_FOREACH(e, p->elements) {
if (element_probe(e, m) < 0) {
p->supported = FALSE;
@@ -2676,6 +2706,12 @@ void pa_alsa_setting_dump(pa_alsa_setting *s) {
s->priority);
}
+void pa_alsa_jack_dump(pa_alsa_jack *j) {
+ pa_assert(j);
+
+ pa_log_debug("Jack %s, alsa_name='%s', detection %s", j->name, j->alsa_name, j->has_control ? "possible" : "unavailable");
+}
+
void pa_alsa_option_dump(pa_alsa_option *o) {
pa_assert(o);
@@ -2711,6 +2747,7 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
void pa_alsa_path_dump(pa_alsa_path *p) {
pa_alsa_element *e;
+ pa_alsa_jack *j;
pa_alsa_setting *s;
pa_assert(p);
@@ -2731,6 +2768,9 @@ void pa_alsa_path_dump(pa_alsa_path *p) {
PA_LLIST_FOREACH(e, p->elements)
pa_alsa_element_dump(e);
+ PA_LLIST_FOREACH(j, p->jacks)
+ pa_alsa_jack_dump(j);
+
PA_LLIST_FOREACH(s, p->settings)
pa_alsa_setting_dump(s);
}
@@ -3084,11 +3124,32 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
PA_HASHMAP_FOREACH(p2, ps->paths, state2) {
pa_alsa_element *ea, *eb;
+ pa_alsa_jack *ja, *jb;
pa_bool_t is_subset = TRUE;
if (p == p2)
continue;
+ /* If a has a jack that b does not have, a is not a subset */
+ PA_LLIST_FOREACH(ja, p->jacks) {
+ pa_bool_t exists = FALSE;
+
+ if (!ja->has_control)
+ continue;
+
+ PA_LLIST_FOREACH(jb, p2->jacks) {
+ if (jb->has_control && !strcmp(jb->alsa_name, ja->alsa_name)) {
+ exists = TRUE;
+ break;
+ }
+ }
+
+ if (!exists) {
+ is_subset = FALSE;
+ break;
+ }
+ }
+
/* Compare the elements of each set... */
pa_assert_se(ea = p->elements);
pa_assert_se(eb = p2->elements);
@@ -3721,6 +3782,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
snd_pcm_t *pcm_handle;
pa_alsa_path_set *ps;
snd_mixer_t *mixer_handle;
+ snd_hctl_t *hctl_handle;
if (direction == PA_ALSA_DIRECTION_OUTPUT) {
if (m->output_path_set)
@@ -3739,8 +3801,8 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
pa_assert(pcm_handle);
- mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL);
- if (!mixer_handle) {
+ mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
+ if (!mixer_handle || !hctl_handle) {
/* Cannot open mixer, remove all entries */
while (pa_hashmap_steal_first(ps->paths));
return;
@@ -3748,7 +3810,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
PA_HASHMAP_FOREACH(p, ps->paths, state) {
- if (pa_alsa_path_probe(p, mixer_handle, m->profile_set->ignore_dB) < 0) {
+ if (pa_alsa_path_probe(p, mixer_handle, hctl_handle, m->profile_set->ignore_dB) < 0) {
pa_hashmap_remove(ps->paths, p);
}
}
@@ -4404,6 +4466,7 @@ static pa_device_port* device_port_alsa_init(pa_hashmap *ports,
data = PA_DEVICE_PORT_DATA(p);
data->path = path;
data->setting = setting;
+ path->port = p;
}
p->is_input |= path->direction == PA_ALSA_DIRECTION_ANY || path->direction == PA_ALSA_DIRECTION_INPUT;
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index d4c7d658..1912ba18 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -38,6 +38,7 @@ typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
typedef struct pa_alsa_setting pa_alsa_setting;
typedef struct pa_alsa_option pa_alsa_option;
typedef struct pa_alsa_element pa_alsa_element;
+typedef struct pa_alsa_jack pa_alsa_jack;
typedef struct pa_alsa_path pa_alsa_path;
typedef struct pa_alsa_path_set pa_alsa_path_set;
typedef struct pa_alsa_mapping pa_alsa_mapping;
@@ -154,11 +155,27 @@ struct pa_alsa_element {
pa_alsa_decibel_fix *db_fix;
};
+struct pa_alsa_jack {
+ pa_alsa_path *path;
+ PA_LLIST_FIELDS(pa_alsa_jack);
+
+ char *name; /* E g "Headphone" */
+ char *alsa_name; /* E g "Headphone Jack" */
+ pa_bool_t has_control; /* is the jack itself present? */
+ pa_bool_t plugged_in; /* is this jack currently plugged in? */
+ snd_hctl_elem_t *hctl_elem; /* Jack detection handle */
+
+ pa_alsa_required_t required;
+ pa_alsa_required_t required_any;
+ pa_alsa_required_t required_absent;
+};
+
/* A path wraps a series of elements into a single entity which can be
* used to control it as if it had a single volume slider, a single
* mute switch and a single list of selectable options. */
struct pa_alsa_path {
pa_alsa_direction_t direction;
+ pa_device_port* port;
char *name;
char *description;
@@ -181,9 +198,11 @@ struct pa_alsa_path {
pa_alsa_element *last_element;
pa_alsa_option *last_option;
pa_alsa_setting *last_setting;
+ pa_alsa_jack *last_jack;
PA_LLIST_HEAD(pa_alsa_element, elements);
PA_LLIST_HEAD(pa_alsa_setting, settings);
+ PA_LLIST_HEAD(pa_alsa_jack, jacks);
};
/* A path set is simply a set of paths that are applicable to a
@@ -197,12 +216,12 @@ int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m);
void pa_alsa_setting_dump(pa_alsa_setting *s);
void pa_alsa_option_dump(pa_alsa_option *o);
-
+void pa_alsa_jack_dump(pa_alsa_jack *j);
void pa_alsa_element_dump(pa_alsa_element *e);
pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction);
pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction);
-int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB);
+int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, snd_hctl_t *hctl, pa_bool_t ignore_dB);
void pa_alsa_path_dump(pa_alsa_path *p);
int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t *muted);
@@ -299,11 +318,11 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);
-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device);
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device, snd_hctl_t **hctl);
pa_alsa_fdlist *pa_alsa_fdlist_new(void);
void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl);
-int pa_alsa_fdlist_set_mixer(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
+int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m);
/* Alternative for handling alsa mixer events in io-thread. */
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 846fd453..c88f4cff 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1841,11 +1841,12 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de
}
static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+ snd_hctl_t *hctl;
if (!mapping && !element)
return;
- if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device, &hctl))) {
pa_log_info("Failed to find a working mixer device.");
return;
}
@@ -1855,7 +1856,7 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT)))
goto fail;
- if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+ if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, hctl, ignore_dB) < 0)
goto fail;
pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
@@ -1946,7 +1947,7 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
u->mixer_fdl = pa_alsa_fdlist_new();
mixer_callback = ctl_mixer_callback;
- if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+ if (pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) < 0) {
pa_log("Failed to initialize file descriptor monitoring");
return -1;
}
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index bc6d723d..3e593403 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1579,11 +1579,12 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char
}
static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+ snd_hctl_t *hctl;
if (!mapping && !element)
return;
- if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device, &hctl))) {
pa_log_info("Failed to find a working mixer device.");
return;
}
@@ -1593,7 +1594,7 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT)))
goto fail;
- if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+ if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, hctl, ignore_dB) < 0)
goto fail;
pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
@@ -1683,7 +1684,7 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
u->mixer_fdl = pa_alsa_fdlist_new();
mixer_callback = ctl_mixer_callback;
- if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+ if (pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) < 0) {
pa_log("Failed to initialize file descriptor monitoring");
return -1;
}
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index d961fbc3..742f38fa 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1440,3 +1440,128 @@ pa_bool_t pa_alsa_may_tsched(pa_bool_t want) {
return TRUE;
}
+
+snd_hctl_elem_t* pa_alsa_find_jack(snd_hctl_t *hctl, const char* jack_name)
+{
+ snd_ctl_elem_id_t *id;
+
+ 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, jack_name);
+
+ return snd_hctl_find_elem(hctl, id);
+}
+
+static int prepare_mixer(snd_mixer_t *mixer, const char *dev, snd_hctl_t **hctl) {
+ int err;
+
+ pa_assert(mixer);
+ pa_assert(dev);
+
+ if ((err = snd_mixer_attach(mixer, dev)) < 0) {
+ pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
+ return -1;
+ }
+
+ /* Note: The hctl handle returned should not be freed.
+ It is closed/freed by alsa-lib on snd_mixer_close/free */
+ if (hctl && (err = snd_mixer_get_hctl(mixer, dev, hctl)) < 0) {
+ pa_log_info("Unable to get hctl of mixer %s: %s", dev, pa_alsa_strerror(err));
+ return -1;
+ }
+
+ if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
+ pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
+ return -1;
+ }
+
+ if ((err = snd_mixer_load(mixer)) < 0) {
+ pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
+ return -1;
+ }
+
+ pa_log_info("Successfully attached to mixer '%s'", dev);
+ return 0;
+}
+
+snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device, snd_hctl_t **hctl) {
+ int err;
+ snd_mixer_t *m;
+ char *md;
+ snd_pcm_info_t* info;
+ snd_pcm_info_alloca(&info);
+
+ if ((err = snd_mixer_open(&m, 0)) < 0) {
+ pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
+ return NULL;
+ }
+
+ /* Then, try by card index */
+ md = pa_sprintf_malloc("hw:%i", alsa_card_index);
+ if (prepare_mixer(m, md, hctl) >= 0) {
+
+ if (ctl_device)
+ *ctl_device = md;
+ else
+ pa_xfree(md);
+
+ return m;
+ }
+
+ pa_xfree(md);
+
+ snd_mixer_close(m);
+ return NULL;
+}
+
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device, snd_hctl_t **hctl) {
+ int err;
+ snd_mixer_t *m;
+ const char *dev;
+ snd_pcm_info_t* info;
+ snd_pcm_info_alloca(&info);
+
+ pa_assert(pcm);
+
+ if ((err = snd_mixer_open(&m, 0)) < 0) {
+ pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
+ return NULL;
+ }
+
+ /* First, try by name */
+ if ((dev = snd_pcm_name(pcm)))
+ if (prepare_mixer(m, dev, hctl) >= 0) {
+ if (ctl_device)
+ *ctl_device = pa_xstrdup(dev);
+
+ return m;
+ }
+
+ /* Then, try by card index */
+ if (snd_pcm_info(pcm, info) >= 0) {
+ char *md;
+ int card_idx;
+
+ if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
+
+ md = pa_sprintf_malloc("hw:%i", card_idx);
+
+ if (!dev || !pa_streq(dev, md))
+ if (prepare_mixer(m, md, hctl) >= 0) {
+
+ if (ctl_device)
+ *ctl_device = md;
+ else
+ pa_xfree(md);
+
+ return m;
+ }
+
+ pa_xfree(md);
+ }
+ }
+
+ snd_mixer_close(m);
+ return NULL;
+}
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index f8d05183..a4beed2b 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -142,4 +142,8 @@ const char* pa_alsa_strerror(int errnum);
pa_bool_t pa_alsa_may_tsched(pa_bool_t want);
+snd_hctl_elem_t* pa_alsa_find_jack(snd_hctl_t *hctl, const char* jack_name);
+
+snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device, snd_hctl_t **hctl);
+
#endif
diff --git a/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
index afac2730..240b5f09 100644
--- a/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
@@ -22,6 +22,9 @@
priority = 78
name = analog-input-microphone-dock
+[Jack Dock Mic]
+required-any = any
+
[Element Dock Mic Boost]
required-any = any
switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-input-front-mic.conf b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
index 852e7e97..0b069f99 100644
--- a/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
@@ -22,6 +22,9 @@
priority = 85
name = analog-input-microphone-front
+[Jack Front Mic]
+required-any = any
+
[Element Front Mic Boost]
required-any = any
switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-input-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf
index a432d6e1..6abafcb9 100644
--- a/src/modules/alsa/mixer/paths/analog-input-linein.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf
@@ -21,6 +21,9 @@
[General]
priority = 81
+[Jack Line]
+required-any = any
+
[Element Capture]
switch = mute
volume = merge
diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf b/src/modules/alsa/mixer/paths/analog-input-mic.conf
index 4cebc4ea..8aaf0cb0 100644
--- a/src/modules/alsa/mixer/paths/analog-input-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf
@@ -22,6 +22,9 @@
priority = 87
name = analog-input-microphone
+[Jack Mic]
+required-any = any
+
[Element Mic Boost]
required-any = any
switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
index e2b2671e..1e6fa578 100644
--- a/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
+++ b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
@@ -22,6 +22,9 @@
priority = 82
name = analog-input-microphone-rear
+[Jack Rear Mic]
+required-any = any
+
[Element Rear Mic Boost]
required-any = any
switch = select
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
index 7f95f0a7..2860f285 100644
--- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
@@ -22,6 +22,9 @@
priority = 90
name = analog-output-headphones
+[Jack Headphone]
+required-any = any
+
[Element Hardware Master]
switch = mute
volume = merge
@@ -39,7 +42,7 @@ switch = off
volume = off
[Element Headphone]
-required = any
+required-any = any
switch = mute
volume = merge
override-map.1 = all
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common
index 93338001..160f222d 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf.common
+++ b/src/modules/alsa/mixer/paths/analog-output.conf.common
@@ -70,7 +70,7 @@
; [Element ...] # For each element that we shall control
; required = ignore | switch | volume | enumeration | any # If set, require this element to be of this kind and available,
; # otherwise don't consider this path valid for the card
-; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements with required-any in this
+; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements or jacks with required-any in this
; # path must be present, otherwise this path is invalid for the card
; required-absent = ignore | switch | volume # If set, require this element to not be of this kind and not
; # available, otherwise don't consider this path valid for the card
@@ -100,6 +100,12 @@
; # channel mask. A channel mask may either be the name of a single channel, or the words "all-left",
; # "all-right", "all-center", "all-front", "all-rear", and "all" to encode a specific subset of
; # channels in a mask
+; [Jack ...] # For each jack that we will use for jack detection
+; # The name 'Jack Foo' must match ALSA's 'Foo Jack' control.
+; required = ignore | any # If not set to ignore, make the path invalid if this jack control is not present.
+; required-absent = ignore | any # If not set to ignore, make the path invalid if this jack control is present.
+; required-any = ignore | any # If not set to ignore, make the path invalid if no jack controls and no elements with
+; # the required-any are present.
[Element PCM]
switch = mute
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 92483e18..c5bffcd2 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -105,6 +105,12 @@ struct userdata {
pa_module *module;
char *device_id;
+ int alsa_card_index;
+
+ snd_mixer_t *mixer_handle;
+ snd_hctl_t *hctl_handle;
+ pa_hashmap *jacks;
+ pa_alsa_fdlist *mixer_fdl;
pa_card *card;
@@ -267,6 +273,115 @@ static void init_profile(struct userdata *u) {
am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am);
}
+static void report_port_state(pa_device_port *p, struct userdata *u)
+{
+ void *state;
+ pa_alsa_jack *jack;
+ pa_port_available_t pa = PA_PORT_AVAILABLE_UNKNOWN;
+
+ PA_HASHMAP_FOREACH(jack, u->jacks, state) {
+ pa_port_available_t cpa;
+
+ if (!jack->path)
+ continue;
+
+ if (p != jack->path->port)
+ continue;
+
+ cpa = jack->plugged_in ? PA_PORT_AVAILABLE_YES : PA_PORT_AVAILABLE_NO;
+
+ /* "Yes" and "no" trumphs "unknown" if we have more than one jack */
+ if (cpa == PA_PORT_AVAILABLE_UNKNOWN)
+ continue;
+
+ if ((cpa == PA_PORT_AVAILABLE_NO && pa == PA_PORT_AVAILABLE_YES) ||
+ (pa == PA_PORT_AVAILABLE_NO && cpa == PA_PORT_AVAILABLE_YES))
+ pa_log_warn("Availability of port '%s' is inconsistent!", p->name);
+ else
+ pa = cpa;
+ }
+
+ pa_device_port_set_available(p, pa);
+}
+
+static int report_jack_state(snd_hctl_elem_t *elem, unsigned int mask)
+{
+ struct userdata *u = snd_hctl_elem_get_callback_private(elem);
+ snd_ctl_elem_value_t *elem_value;
+ pa_bool_t plugged_in;
+ void *state;
+ pa_alsa_jack *jack;
+
+ pa_assert(u);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ snd_ctl_elem_value_alloca(&elem_value);
+ if (snd_hctl_elem_read(elem, elem_value) < 0) {
+ pa_log_warn("Failed to read jack detection from '%s'", pa_strnull(snd_hctl_elem_get_name(elem)));
+ return 0;
+ }
+
+ plugged_in = !!snd_ctl_elem_value_get_boolean(elem_value, 0);
+
+ pa_log_debug("Jack '%s' is now %s", pa_strnull(snd_hctl_elem_get_name(elem)), plugged_in ? "plugged in" : "unplugged");
+
+ PA_HASHMAP_FOREACH(jack, u->jacks, state)
+ if (jack->hctl_elem == elem) {
+ jack->plugged_in = plugged_in;
+ pa_assert(jack->path && jack->path->port);
+ report_port_state(jack->path->port, u);
+ }
+ return 0;
+}
+
+static void init_jacks(struct userdata *u) {
+ void *state;
+ pa_alsa_path* path;
+ pa_alsa_jack* jack;
+
+ u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ /* See if we have any jacks */
+ if (u->profile_set->output_paths)
+ PA_HASHMAP_FOREACH(path, u->profile_set->output_paths, state)
+ PA_LLIST_FOREACH(jack, path->jacks)
+ if (jack->has_control)
+ pa_hashmap_put(u->jacks, jack, jack);
+
+ if (u->profile_set->input_paths)
+ PA_HASHMAP_FOREACH(path, u->profile_set->input_paths, state)
+ PA_LLIST_FOREACH(jack, path->jacks)
+ if (jack->has_control)
+ pa_hashmap_put(u->jacks, jack, jack);
+
+ pa_log_debug("Found %d jacks.", pa_hashmap_size(u->jacks));
+
+ if (pa_hashmap_size(u->jacks) == 0)
+ return;
+
+ u->mixer_fdl = pa_alsa_fdlist_new();
+
+ u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL, &u->hctl_handle);
+ if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, NULL, u->hctl_handle, u->core->mainloop) >= 0) {
+ PA_HASHMAP_FOREACH(jack, u->jacks, state) {
+ jack->hctl_elem = pa_alsa_find_jack(u->hctl_handle, jack->alsa_name);
+ if (!jack->hctl_elem) {
+ pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
+ jack->has_control = FALSE;
+ continue;
+ }
+ snd_hctl_elem_set_callback_private(jack->hctl_elem, u);
+ snd_hctl_elem_set_callback(jack->hctl_elem, report_jack_state);
+ report_jack_state(jack->hctl_elem, 0);
+ }
+
+ } else
+ pa_log("Failed to open hctl/mixer for jack detection");
+
+}
+
static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) {
char *t;
const char *n;
@@ -296,7 +411,6 @@ static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *de
int pa__init(pa_module *m) {
pa_card_new_data data;
pa_modargs *ma;
- int alsa_card_index;
pa_bool_t ignore_dB = FALSE;
struct userdata *u;
pa_reserve_wrapper *reserve = NULL;
@@ -325,8 +439,8 @@ int pa__init(pa_module *m) {
u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID));
u->modargs = ma;
- if ((alsa_card_index = snd_card_get_index(u->device_id)) < 0) {
- pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(alsa_card_index));
+ if ((u->alsa_card_index = snd_card_get_index(u->device_id)) < 0) {
+ pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(u->alsa_card_index));
goto fail;
}
@@ -343,7 +457,7 @@ int pa__init(pa_module *m) {
}
#ifdef HAVE_UDEV
- fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET");
+ fn = pa_udev_get_property(u->alsa_card_index, "PULSE_PROFILE_SET");
#endif
if (pa_modargs_get_value(ma, "profile_set", NULL)) {
@@ -366,7 +480,7 @@ int pa__init(pa_module *m) {
data.driver = __FILE__;
data.module = m;
- pa_alsa_init_proplist_card(m->core, data.proplist, alsa_card_index);
+ pa_alsa_init_proplist_card(m->core, data.proplist, u->alsa_card_index);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id);
pa_alsa_init_description(data.proplist);
@@ -418,6 +532,7 @@ int pa__init(pa_module *m) {
u->card->set_profile = card_set_profile;
init_profile(u);
+ init_jacks(u);
if (reserve)
pa_reserve_wrapper_unref(reserve);
@@ -469,6 +584,13 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
goto finish;
+ if (u->mixer_fdl)
+ pa_alsa_fdlist_free(u->mixer_fdl);
+ if (u->mixer_handle)
+ snd_mixer_close(u->mixer_handle);
+ if (u->jacks)
+ pa_hashmap_free(u->jacks, NULL, NULL);
+
if (u->card && u->card->sinks) {
pa_sink *s;