diff options
-rw-r--r-- | src/modules/alsa/alsa-mixer.c | 239 | ||||
-rw-r--r-- | src/modules/alsa/alsa-mixer.h | 27 | ||||
-rw-r--r-- | src/modules/alsa/alsa-sink.c | 7 | ||||
-rw-r--r-- | src/modules/alsa/alsa-source.c | 7 | ||||
-rw-r--r-- | src/modules/alsa/alsa-util.c | 125 | ||||
-rw-r--r-- | src/modules/alsa/alsa-util.h | 4 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-input-dock-mic.conf | 3 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-input-front-mic.conf | 3 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-input-linein.conf | 3 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-input-mic.conf | 3 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-input-rear-mic.conf | 3 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-output-headphones.conf | 5 | ||||
-rw-r--r-- | src/modules/alsa/mixer/paths/analog-output.conf.common | 8 | ||||
-rw-r--r-- | src/modules/alsa/module-alsa-card.c | 132 |
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; |