diff --git a/NEWS b/NEWS index 782a612e1..72dd76fda 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,154 @@ +PulseAudio 14.2 + +A bug fix release. + + * Fix port switching when unplugging headphones + +Contributors + + Tanu Kaskinen + + +PulseAudio 14.1 + +A bug fix release. + + * Support upto 8 mixer channels on ALSA devices + * Handle ALSA jacks with the same name but different index values + * Switch to plugged-in headset when mic availability is unknown + * Fix a potential segfault in the Bluetooth oFono HFP backend + * Fix a problem with module-ladspa-sink when avoid-resampling=true + * Fix database names containing canonical host for meson builds + +Contributors + + Arun Raghavan + Hui Wang + Igor V. Kovalenko + Jaroslav Kysela + Kai-Heng Feng + Patrick Gaskin + Tanu Kaskinen + morrishoresh + + +PulseAudio 14.0 + +Changes at a glance: + + * Notes for end users + * Significant routing changes to default sinks/sources + * Changing the default sink moves streams from the old default sink to the new + * Moving a stream to the default sink removes the "manually routed" status of the stream + * If a sink changes status to available, streams that prefer that sink move there automatically + * The same changes have been applied to the source stream routing + * Workaround for GNOME Sound Settings' stream routing behaviour + * module-rescue-streams is deprecated, functionality moved to the core + * New rescue-streams option in daemon.conf + * Automatic switching to HDMI is now disabled by default + * Better support for some USB gaming headsets + * Flat volumes are now disabled by default + * The RAOP sink can be configured to automatically reconnect on connection failures + * Separate sink_channels and source_channels for module-jackdbus-detect + * Improved support for ALSA UCM + * Support for ALSA mixer controls with non-zero index + * It's now possible to set intended roles for devices in the ALSA profile configuration + * Ports now have a type associated with them + * Mappings have a new "description-key" option in the ALSA profile configuration + * New xauthority argument for X11 modules + * module-null-sink's compressed format support can now be configured at run-time + * The enable-lfe-remixing option in daemon.conf was split into remixing-produce-lfe and remixing-consume-lfe + * New channel_map argument for module-raop-sink + * Notes for application developers + * pa_mainloop_prepare interprets the timeout argument as microseconds again + * New availability_group and type fields in the port info structs + * New macros: PA_LIKELY(), PA_UNLIKELY(), PA_CLAMP() and PA_CLAMP_UNLIKELY() + * Notes for packagers + * New GStreamer-based RTP implementation + * qpaeq switched from Python 2 to Python 3 + * Compile-time option to forget pre-14.0 stream routing + * The install path of the ALSA configuration files is now configurable + * GNU gettext minimum version requirement bumped from 0.19.3 to 0.19.8 + * Heads-up: dropping autotools build system + * Heads-up: dropping EsounD support is considered, tell us if you still need it + * Heads-up: dropping GConf support + +Detailed change log: + + https://www.freedesktop.org/wiki/Software/PulseAudio/Notes/14.0/ + +Contributors + + Alexander E. Patrakov + Arun Raghavan + Balázs Meskó + Baurzhan Muftakhidinov + Ben Buchwald + Daniil Kovalev + Dave Chiluk + David Heidelberg + Dusan Kazik + Eero Nurkkala + Emanuil Novachev + Emilio Herrera + Felipe Sateler + Felix Yan + Geert Warrink + Georg Chini + Göran Uddeborg + Hugo Osvaldo Barrera + Hui Wang + Igor V. Kovalenko + Jan Alexander Steffens + Jarno Suni + Jaroslav Kysela + Jaska Uimonen + Jean-Baptiste Holcroft + Josh + Juliano de Souza Camargo + Kai-Heng Feng + Karl Ove Hufthammer + Khem Raj + Krzysztof Stasiowski + Laurent Bigonville + Libin Yang + Marc Ranolfi + Michael Pivonka + Milo Casagrande + Milo Ivir + Nick Moriarty + Oğuz Ersen + Pali Rohár + Peter Levine + Peter Meerwald + Philip Withnall + Piotr Drąg + RODRIGUEZ Christophe + Rafael Fontenelle + Ralph Seichter + Rasmus Thomsen + Rickie Schroeder + Rosen Penev + Ryszard Knop + Sanchayan Maity + Sebastian Dröge + Sebastien + StefanBruens + Taahir Ahmed + Tanu Kaskinen + Timo Gurr + Tom Yan + Tomasz Kontusz + Vasilis Tsiligiannis + Wim Taymans + Yi-Jyun Pan + Yuri Chornoivan + itsthem + muzena + roshal + zhaochengyi + + PulseAudio 13.0 Changes at a glance: diff --git a/configure.ac b/configure.ac index a519e63d7..680989708 100644 --- a/configure.ac +++ b/configure.ac @@ -56,9 +56,6 @@ AC_SUBST(LIBPULSE_SIMPLE_VERSION_INFO, [1:1:1]) # info x:y:z always will hold x=z AC_SUBST(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO, [0:6:0]) -AC_CANONICAL_HOST -AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.]) - AC_CHECK_PROG([STOW], [stow], [yes], [no]) AS_IF([test "x$STOW" = "xyes" && test -d /usr/local/stow], [ diff --git a/meson.build b/meson.build index 658eeee57..a5263215c 100644 --- a/meson.build +++ b/meson.build @@ -125,7 +125,6 @@ cdata = configuration_data() cdata.set_quoted('PACKAGE', 'pulseaudio') cdata.set_quoted('PACKAGE_NAME', 'pulseaudio') cdata.set_quoted('PACKAGE_VERSION', pa_version_str) -cdata.set_quoted('CANONICAL_HOST', host_machine.cpu()) cdata.set('PA_MAJOR', pa_version_major) cdata.set('PA_MINOR', pa_version_minor) cdata.set('PA_API_VERSION', pa_api_version) diff --git a/src/Makefile.am b/src/Makefile.am index bd764037b..454b644bb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1037,7 +1037,7 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/source.c pulsecore/source.h \ pulsecore/start-child.c pulsecore/start-child.h \ pulsecore/thread-mq.c pulsecore/thread-mq.h \ - pulsecore/database.h + pulsecore/database.c pulsecore/database.h libpulsecore_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(LIBSNDFILE_CFLAGS) $(WINSOCK_CFLAGS) libpulsecore_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version diff --git a/src/daemon/main.c b/src/daemon/main.c index 59f931219..30ef49964 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -916,7 +916,6 @@ int main(int argc, char *argv[]) { pa_set_env_and_record("PULSE_SYSTEM", conf->system_instance ? "1" : "0"); pa_log_info("This is PulseAudio %s", PACKAGE_VERSION); - pa_log_debug("Compilation host: %s", CANONICAL_HOST); pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS); #ifdef HAVE_LIBSAMPLERATE diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index 063179052..e494a1282 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -113,7 +113,7 @@ struct description2_map { pa_device_port_type_t type; }; -static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { +char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { if (id->index > 0) { snprintf(dst, dst_len, "'%s',%d", id->name, id->index); } else { @@ -153,7 +153,7 @@ static int alsa_id_decode(const char *src, char *name, int *index) { return 0; } -pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) { +pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index) { pa_alsa_jack *jack; pa_assert(name); @@ -162,7 +162,8 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name jack->path = path; jack->mixer_device_name = pa_xstrdup(mixer_device_name); jack->name = pa_xstrdup(name); - jack->alsa_name = pa_sprintf_malloc("%s Jack", name); + jack->alsa_id.name = pa_sprintf_malloc("%s Jack", name); + jack->alsa_id.index = index; jack->state_unplugged = PA_AVAILABLE_NO; jack->state_plugged = PA_AVAILABLE_YES; jack->ucm_devices = pa_dynarray_new(NULL); @@ -177,7 +178,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) { pa_dynarray_free(jack->ucm_hw_mute_devices); pa_dynarray_free(jack->ucm_devices); - pa_xfree(jack->alsa_name); + pa_xfree(jack->alsa_id.name); pa_xfree(jack->name); pa_xfree(jack->mixer_device_name); pa_xfree(jack); @@ -689,6 +690,20 @@ static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_M [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN }; +static snd_mixer_selem_channel_id_t alsa_channel_positions[POSITION_MASK_CHANNELS] = { + SND_MIXER_SCHN_FRONT_LEFT, + SND_MIXER_SCHN_FRONT_RIGHT, + SND_MIXER_SCHN_REAR_LEFT, + SND_MIXER_SCHN_REAR_RIGHT, + SND_MIXER_SCHN_FRONT_CENTER, + SND_MIXER_SCHN_WOOFER, + SND_MIXER_SCHN_SIDE_LEFT, + SND_MIXER_SCHN_SIDE_RIGHT, +#if POSITION_MASK_CHANNELS > 8 +#error "Extend alsa_channel_positions[] array (9+)" +#endif +}; + static void setting_free(pa_alsa_setting *s) { pa_assert(s); @@ -821,7 +836,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -847,14 +862,14 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann if (value < e->db_fix->min_step) { value = e->db_fix->min_step; snd_mixer_selem_set_playback_volume(me, c, value); - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } else if (value > e->db_fix->max_step) { value = e->db_fix->max_step; snd_mixer_selem_set_playback_volume(me, c, value); - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); @@ -877,14 +892,14 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann if (value < e->db_fix->min_step) { value = e->db_fix->min_step; snd_mixer_selem_set_capture_volume(me, c, value); - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } else if (value > e->db_fix->max_step) { value = e->db_fix->max_step; snd_mixer_selem_set_capture_volume(me, c, value); - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); @@ -992,7 +1007,7 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) { SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1158,7 +1173,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1350,7 +1365,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1361,7 +1376,7 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { r = snd_mixer_selem_set_capture_switch_all(me, b); if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); } @@ -1405,7 +1420,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -1450,7 +1465,7 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { } if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno)); } @@ -1656,19 +1671,19 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r)); return false; } if (e->min_volume >= e->max_volume) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.", buf, e->min_volume, e->max_volume); return false; } if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", e->constant_volume, buf, e->min_volume, e->max_volume); return false; @@ -1676,7 +1691,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " "real hardware range (%li-%li). Disabling the decibel fix.", buf, e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); @@ -1703,19 +1718,19 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { long max_dB_checked = 0; if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume); return false; } if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume); return false; } if (min_dB != min_dB_checked || max_dB != max_dB_checked) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0, @@ -1738,7 +1753,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { if (e->volume_limit >= 0) { if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " "%li-%li. The volume limit is ignored.", buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); @@ -1750,7 +1765,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { e->db_fix->max_step = e->max_volume; e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r)); e->has_dB = false; } else @@ -1767,7 +1782,11 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { if (is_mono) { e->n_channels = 1; - if (!e->override_map) { + if ((e->override_map & (1 << (e->n_channels-1))) && e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] == 0) { + pa_log_warn("Override map for mono element %s is invalid, ignoring override map", e->path->name); + e->override_map &= ~(1 << (e->n_channels-1)); + } + if (!(e->override_map & (1 << (e->n_channels-1)))) { for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) continue; @@ -1791,27 +1810,28 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { } if (e->n_channels <= 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume element %s with no channels?", buf); return false; - } else if (e->n_channels > 2) { + } else if (e->n_channels > POSITION_MASK_CHANNELS) { /* FIXME: In some places code like this is used: * * e->masks[alsa_channel_ids[p]][e->n_channels-1] * * The definition of e->masks is * - * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2]; + * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS]; * - * Since the array size is fixed at 2, we obviously - * don't support elements with more than two + * Since the array size is fixed at POSITION_MASK_CHANNELS, we obviously + * don't support elements with more than POSITION_MASK_CHANNELS * channels... */ - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels); return false; } - if (!e->override_map) { +retry: + if (!(e->override_map & (1 << (e->n_channels-1)))) { for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { bool has_channel; @@ -1834,6 +1854,17 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; } + + if (e->merged_mask == 0) { + if (!(e->override_map & (1 << (e->n_channels-1)))) { + pa_log_warn("Channel map for element %s is invalid", e->path->name); + return false; + } + pa_log_warn("Override map for element %s has empty result, ignoring override map", e->path->name); + e->override_map &= ~(1 << (e->n_channels-1)); + goto retry; + } + return true; } @@ -1943,12 +1974,12 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) } new_name = pa_sprintf_malloc("%s,pcm=%i Jack", j->name, mapping->hw_device_index); - pa_xfree(j->alsa_name); - j->alsa_name = new_name; + pa_xfree(j->alsa_id.name); + j->alsa_id.name = new_name; j->append_pcm_to_name = false; } - has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL; + has_control = pa_alsa_mixer_find_card(m, &j->alsa_id, 0) != NULL; pa_alsa_jack_set_has_control(j, has_control); if (j->has_control) { @@ -2011,19 +2042,26 @@ finish: static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) { pa_alsa_jack *j; + char *name; + int index; if (!pa_startswith(section, "Jack ")) return NULL; section += 5; - if (p->last_jack && pa_streq(p->last_jack->name, section)) + name = alloca(strlen(section) + 1); + if (alsa_id_decode(section, name, &index)) + return NULL; + + if (p->last_jack && pa_streq(p->last_jack->name, name) && + p->last_jack->alsa_id.index == index) return p->last_jack; PA_LLIST_FOREACH(j, p->jacks) - if (pa_streq(j->name, section)) + if (pa_streq(j->name, name) && j->alsa_id.index == index) goto finish; - j = pa_alsa_jack_new(p, NULL, section); + j = pa_alsa_jack_new(p, NULL, name, index); PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j); finish: @@ -2427,6 +2465,16 @@ static int element_parse_volume_limit(pa_config_parser_state *state) { return 0; } +static unsigned int parse_channel_position(const char *m) +{ + pa_channel_position_t p; + + if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID) + return SND_MIXER_SCHN_UNKNOWN; + + return alsa_channel_ids[p]; +} + static pa_channel_position_mask_t parse_mask(const char *m) { pa_channel_position_mask_t v; @@ -2464,7 +2512,9 @@ static int element_parse_override_map(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; const char *split_state = NULL; + char *s; unsigned i = 0; + int channel_count = 0; char *n; pa_assert(state); @@ -2476,31 +2526,60 @@ static int element_parse_override_map(pa_config_parser_state *state) { return -1; } + s = strstr(state->lvalue, "."); + if (s) { + pa_atoi(s + 1, &channel_count); + if (channel_count < 1 || channel_count > POSITION_MASK_CHANNELS) { + pa_log("[%s:%u] Override map index '%s' invalid in '%s'", state->filename, state->lineno, state->lvalue, state->section); + return 0; + } + } else { + pa_log("[%s:%u] Invalid override map syntax '%s' in '%s'", state->filename, state->lineno, state->lvalue, state->section); + return -1; + } + while ((n = pa_split(state->rvalue, ",", &split_state))) { pa_channel_position_mask_t m; + snd_mixer_selem_channel_id_t channel_position; + + if (i >= (unsigned)channel_count) { + pa_log("[%s:%u] Invalid override map size (>%d) in '%s'", state->filename, state->lineno, channel_count, state->section); + return -1; + } + channel_position = alsa_channel_positions[i]; if (!*n) m = 0; else { - if ((m = parse_mask(n)) == 0) { - pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, n, state->section); + s = strstr(n, ":"); + if (s) { + *s = '\0'; + s++; + channel_position = parse_channel_position(n); + if (channel_position == SND_MIXER_SCHN_UNKNOWN) { + pa_log("[%s:%u] Override map position '%s' invalid in '%s'", state->filename, state->lineno, n, state->section); + pa_xfree(n); + return -1; + } + } + if ((m = parse_mask(s ? s : n)) == 0) { + pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, s ? s : n, state->section); pa_xfree(n); return -1; } } - if (pa_streq(state->lvalue, "override-map.1")) - e->masks[i++][0] = m; - else - e->masks[i++][1] = m; - - /* Later on we might add override-map.3 and so on here ... */ - + if (e->masks[channel_position][channel_count-1]) { + pa_log("[%s:%u] Override map '%s' duplicate position '%s' in '%s'", state->filename, state->lineno, s ? s : n, snd_mixer_selem_channel_name(channel_position), state->section); + pa_xfree(n); + return -1; + } + e->override_map |= (1 << (channel_count - 1)); + e->masks[channel_position][channel_count-1] = m; pa_xfree(n); + i++; } - e->override_map = true; - return 0; } @@ -2574,7 +2653,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } @@ -2587,7 +2666,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); if (r < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); } @@ -2595,7 +2674,7 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno)); } } @@ -2652,7 +2731,7 @@ static int option_verify(pa_alsa_option *o) { if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT && o->element->switch_use != PA_ALSA_SWITCH_SELECT) { - alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id); pa_log("Element %s of option %s not set for select.", buf, o->name); return -1; } @@ -2660,7 +2739,7 @@ static int option_verify(pa_alsa_option *o) { if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && !pa_streq(o->alsa_name, "on") && !pa_streq(o->alsa_name, "off")) { - alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id); pa_log("Switch %s options need be named off or on ", buf); return -1; } @@ -2686,13 +2765,13 @@ static int element_verify(pa_alsa_element *e) { (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) || (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) || (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log("Element %s cannot be required and absent at the same time.", buf); return -1; } if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log("Element %s cannot set select for both switch and enumeration.", buf); return -1; } @@ -2718,6 +2797,7 @@ static int path_verify(pa_alsa_path *p) { { "analog-input-video", N_("Video"), PA_DEVICE_PORT_TYPE_VIDEO }, { "analog-output", N_("Analog Output"), PA_DEVICE_PORT_TYPE_ANALOG }, { "analog-output-headphones", N_("Headphones"), PA_DEVICE_PORT_TYPE_HEADPHONES }, + { "analog-output-headphones-2", N_("Headphones 2"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-headphones-mono", N_("Headphones Mono Output"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-lineout", N_("Line Out"), PA_DEVICE_PORT_TYPE_LINE }, { "analog-output-mono", N_("Analog Mono Output"), PA_DEVICE_PORT_TYPE_ANALOG }, @@ -2800,6 +2880,15 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa { "enumeration", element_parse_enumeration, NULL, NULL }, { "override-map.1", element_parse_override_map, NULL, NULL }, { "override-map.2", element_parse_override_map, NULL, NULL }, + { "override-map.3", element_parse_override_map, NULL, NULL }, + { "override-map.4", element_parse_override_map, NULL, NULL }, + { "override-map.5", element_parse_override_map, NULL, NULL }, + { "override-map.6", element_parse_override_map, NULL, NULL }, + { "override-map.7", element_parse_override_map, NULL, NULL }, + { "override-map.8", element_parse_override_map, NULL, NULL }, +#if POSITION_MASK_CHANNELS > 8 +#error "Add override-map.9+ definitions" +#endif /* ... later on we might add override-map.3 and so on here ... */ { "required", element_parse_required, NULL, NULL }, { "required-any", element_parse_required, NULL, NULL }, @@ -3017,6 +3106,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m 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; + bool min_dB_set, max_dB_set; char buf[64]; pa_assert(p); @@ -3032,22 +3122,23 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m pa_log_debug("Probing path '%s'", p->name); PA_LLIST_FOREACH(j, p->jacks) { + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &j->alsa_id); if (jack_probe(j, mapping, m) < 0) { p->supported = false; - pa_log_debug("Probe of jack '%s' failed.", j->alsa_name); + pa_log_debug("Probe of jack %s failed.", buf); return -1; } - pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found"); + pa_log_debug("Probe of jack %s succeeded (%s)", buf, j->has_control ? "found!" : "not found"); } PA_LLIST_FOREACH(e, p->elements) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); if (element_probe(e, m) < 0) { p->supported = false; pa_log_debug("Probe of element %s failed.", buf); return -1; } - pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use); + pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d, has_dB=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use, e->has_dB); if (ignore_dB) e->has_dB = false; @@ -3111,18 +3202,30 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m p->supported = true; p->min_dB = INFINITY; + min_dB_set = false; p->max_dB = -INFINITY; + max_dB_set = false; for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) { if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) { - if (p->min_dB > min_dB[t]) + if (p->min_dB > min_dB[t]) { p->min_dB = min_dB[t]; + min_dB_set = true; + } - if (p->max_dB < max_dB[t]) + if (p->max_dB < max_dB[t]) { p->max_dB = max_dB[t]; + max_dB_set = true; + } } } + /* this is probably a wrong prediction, but it should be safe */ + if (!min_dB_set) + p->min_dB = -INFINITY; + if (!max_dB_set) + p->max_dB = 0; + return 0; } @@ -3138,7 +3241,7 @@ void pa_alsa_setting_dump(pa_alsa_setting *s) { 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"); + pa_log_debug("Jack %s, alsa_name='%s', index='%d', detection %s", j->name, j->alsa_id.name, j->alsa_id.index, j->has_control ? "possible" : "unavailable"); } void pa_alsa_option_dump(pa_alsa_option *o) { @@ -3158,8 +3261,8 @@ void pa_alsa_element_dump(pa_alsa_element *e) { pa_alsa_option *o; pa_assert(e); - alsa_id_str(buf, sizeof(buf), &e->alsa_id); - pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s", + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); + pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%02x", buf, e->direction, e->switch_use, @@ -3171,7 +3274,7 @@ void pa_alsa_element_dump(pa_alsa_element *e) { e->required_absent, (long long unsigned) e->merged_mask, e->n_channels, - pa_yes_no(e->override_map)); + e->override_map); PA_LLIST_FOREACH(o, e->options) pa_alsa_option_dump(o); @@ -3218,7 +3321,7 @@ static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_e SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return; } @@ -3513,7 +3616,7 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ SELEM_INIT(sid, &a->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { - alsa_id_str(buf, sizeof(buf), &a->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return false; } @@ -3544,7 +3647,7 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ return false; for (s = 0; s <= SND_MIXER_SCHN_LAST; s++) if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) { - alsa_id_str(buf, sizeof(buf), &a->alsa_id); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id); pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d", buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); return false; @@ -3621,7 +3724,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) { continue; PA_LLIST_FOREACH(jb, p2->jacks) { - if (jb->has_control && pa_streq(jb->alsa_name, ja->alsa_name) && + if (jb->has_control && pa_streq(ja->alsa_id.name, jb->alsa_id.name) && + (ja->alsa_id.index == jb->alsa_id.index) && (ja->state_plugged == jb->state_plugged) && (ja->state_unplugged == jb->state_unplugged)) { exists = true; @@ -4311,7 +4415,8 @@ static void profile_set_set_availability_groups(pa_alsa_profile_set *ps) { PA_LLIST_FOREACH(j2, p2->jacks) { if (!j2->has_control || j2->state_plugged == PA_AVAILABLE_NO) continue; - if (pa_streq(j->alsa_name, j2->alsa_name)) { + if (pa_streq(j->alsa_id.name, j2->alsa_id.name) && + j->alsa_id.index == j2->alsa_id.index) { j->state_plugged = PA_AVAILABLE_UNKNOWN; j2->state_plugged = PA_AVAILABLE_UNKNOWN; found = p2->availability_group; diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h index 905e3128b..db8310258 100644 --- a/src/modules/alsa/alsa-mixer.h +++ b/src/modules/alsa/alsa-mixer.h @@ -50,6 +50,8 @@ typedef struct pa_alsa_port_data pa_alsa_port_data; #include "alsa-util.h" #include "alsa-ucm.h" +#define POSITION_MASK_CHANNELS 8 + typedef enum pa_alsa_switch_use { PA_ALSA_SWITCH_IGNORE, PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */ @@ -113,6 +115,8 @@ struct pa_alsa_mixer_id { int index; }; +char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id); + /* An option belongs to an element and refers to one enumeration item * of the element is an enumeration item, or a switch status if the * element is a switch item. */ @@ -152,7 +156,7 @@ struct pa_alsa_element { long constant_volume; - bool override_map:1; + unsigned int override_map; bool direction_try_other:1; bool has_dB:1; @@ -160,7 +164,7 @@ struct pa_alsa_element { long volume_limit; /* -1 for no configured limit */ double min_dB, max_dB; - pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2]; + pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS]; unsigned n_channels; pa_channel_position_mask_t merged_mask; @@ -177,8 +181,8 @@ struct pa_alsa_jack { snd_mixer_t *mixer_handle; char *mixer_device_name; + struct pa_alsa_mixer_id alsa_id; char *name; /* E g "Headphone" */ - char *alsa_name; /* E g "Headphone Jack" */ bool has_control; /* is the jack itself present? */ bool plugged_in; /* is this jack currently plugged in? */ snd_mixer_elem_t *melem; /* Jack detection handle */ @@ -194,7 +198,7 @@ struct pa_alsa_jack { bool append_pcm_to_name; }; -pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name); +pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index); void pa_alsa_jack_free(pa_alsa_jack *jack); void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control); void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in); diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c index 18925b792..d9cea6105 100644 --- a/src/modules/alsa/alsa-ucm.c +++ b/src/modules/alsa/alsa-ucm.c @@ -1719,7 +1719,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control); return NULL; } - j = pa_alsa_jack_new(NULL, mixer_device_name, name); + j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0); PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j); finish: @@ -1953,7 +1953,7 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) { continue; } - has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL; + has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL; pa_alsa_jack_set_has_control(dev->jack, has_control); pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); } diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index bf35b6112..172a7bb51 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -1635,8 +1635,8 @@ static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, return NULL; } -snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device) { - return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, name, 0, device); +snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) { + return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device); } snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) { diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index cb8be22fb..2eed3eac3 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -148,7 +148,7 @@ const char* pa_alsa_strerror(int errnum); bool pa_alsa_may_tsched(bool want); -snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device); +snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device); snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device); snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe); diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf index 30815d0a8..178999088 100644 --- a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf +++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf @@ -13,17 +13,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . -; Path for mixers that have a 'Headphone2' control +; Path for the second headphone output on dual-headphone machines. ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 98 -description-key = analog-output-headphones [Properties] device.icon_name = audio-headphones +; HP EliteDesk 800 SFF Headphone +[Jack Front Headphone,1] +required-any = any + +; HP EliteDesk 800 DM Headphone +[Jack Front Headphone Surround] +required-any = any + [Element Hardware Master] switch = mute volume = merge @@ -47,6 +54,13 @@ volume = off switch = mute volume = zero +[Element Headphone,1] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + [Element Headphone+LO] switch = mute volume = zero @@ -56,7 +70,7 @@ switch = off volume = off [Element Headphone2] -required = any +required-any = any switch = mute volume = merge override-map.1 = all diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf index d4ad7777d..88907f081 100644 --- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf +++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf @@ -35,6 +35,10 @@ state.unplugged = unknown [Jack Front Headphone] required-any = any +; HP EliteDesk 800 DM Headset +[Jack Front Headphone Front] +required-any = any + [Jack Front Headphone Phantom] required-any = any state.plugged = unknown @@ -89,6 +93,13 @@ volume = merge override-map.1 = all override-map.2 = all-left,all-right +; This path is intended to control the first headphones, not +; the second headphones. But it should not hurt if we leave the second +; headphone jack enabled nonetheless. +[Element Headphone,1] +switch = mute +volume = zero + [Element Headset] required-any = any switch = mute diff --git a/src/modules/alsa/mixer/paths/analog-output-lineout.conf b/src/modules/alsa/mixer/paths/analog-output-lineout.conf index 9a6af3ad8..2dde15923 100644 --- a/src/modules/alsa/mixer/paths/analog-output-lineout.conf +++ b/src/modules/alsa/mixer/paths/analog-output-lineout.conf @@ -127,6 +127,10 @@ required-any = any switch = off volume = off +[Element Headphone,1] +switch = off +volume = off + [Element Headphone2] switch = off volume = off diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf index 989654334..5e4940598 100644 --- a/src/modules/alsa/mixer/paths/analog-output-mono.conf +++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf @@ -44,6 +44,10 @@ override-map.2 = all-left,all-right switch = mute volume = zero +[Element Headphone,1] +switch = mute +volume = zero + [Element Headphone+LO] switch = mute volume = zero diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf index 71f356dce..4ee72f504 100644 --- a/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf +++ b/src/modules/alsa/mixer/paths/analog-output-speaker-always.conf @@ -76,6 +76,10 @@ volume = off switch = mute volume = zero +[Element Headphone,1] +switch = mute +volume = zero + [Element Headphone2] switch = mute volume = zero diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf index 27a3983d5..fcf2f5c2f 100644 --- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf +++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf @@ -94,6 +94,10 @@ volume = off switch = off volume = off +[Element Headphone,1] +switch = off +volume = off + [Element Headphone2] switch = off volume = off diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index de2fe9cc4..08e655e26 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -621,6 +621,7 @@ static void init_jacks(struct userdata *u) { void *state; pa_alsa_path* path; pa_alsa_jack* jack; + char buf[64]; u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); @@ -663,9 +664,10 @@ static void init_jacks(struct userdata *u) { } } pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop); - jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0); + jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0); if (!jack->melem) { - pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); + pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id); + pa_log_warn("Jack %s seems to have disappeared.", buf); pa_alsa_jack_set_has_control(jack, false); continue; } diff --git a/src/modules/bluetooth/backend-ofono.c b/src/modules/bluetooth/backend-ofono.c index 0e5bbe8b7..d7a13efd0 100644 --- a/src/modules/bluetooth/backend-ofono.c +++ b/src/modules/bluetooth/backend-ofono.c @@ -627,8 +627,6 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage card = pa_hashmap_get(backend->cards, path); - card->connecting = false; - if (!card || codec != HFP_AUDIO_CODEC_CVSD || card->fd >= 0) { pa_log_warn("New audio connection invalid arguments (path=%s fd=%d, codec=%d)", path, fd, codec); pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Invalid arguments in method call")); @@ -639,6 +637,7 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", path, fd, codec); + card->connecting = false; card->fd = fd; card->transport->codec = codec; diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index 80506cd04..b35cf3e7e 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -618,7 +618,7 @@ static pa_hook_result_t card_preferred_port_changed_callback(pa_core *core, pa_c int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname; + char *state_path; bool restore_bluetooth_profile; pa_assert(m); @@ -648,17 +648,15 @@ int pa__init(pa_module*m) { pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_added_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) port_offset_change_callback, u); - if (!(fname = pa_state_path("card-database", true))) + if (!(state_path = pa_state_path(NULL, true))) goto fail; - if (!(u->database = pa_database_open(fname, true))) { - pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); - pa_xfree(fname); + if (!(u->database = pa_database_open(state_path, "card-database", true, true))) { + pa_xfree(state_path); goto fail; } - pa_log_info("Successfully opened database file '%s'.", fname); - pa_xfree(fname); + pa_xfree(state_path); pa_modargs_free(ma); return 0; diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c index 308ef0b57..b51b6c8a0 100644 --- a/src/modules/module-device-manager.c +++ b/src/modules/module-device-manager.c @@ -1544,7 +1544,7 @@ struct prioritised_indexes { int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname; + char *state_path; pa_sink *sink; pa_source *source; uint32_t idx; @@ -1601,17 +1601,15 @@ int pa__init(pa_module*m) { u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u); } - if (!(fname = pa_state_path("device-manager", true))) + if (!(state_path = pa_state_path(NULL, true))) goto fail; - if (!(u->database = pa_database_open(fname, true))) { - pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); - pa_xfree(fname); + if (!(u->database = pa_database_open(state_path, "device-manager", true, true))) { + pa_xfree(state_path); goto fail; } - pa_log_info("Successfully opened database file '%s'.", fname); - pa_xfree(fname); + pa_xfree(state_path); /* Attempt to inject the devices into the list in priority order */ total_devices = PA_MAX(pa_idxset_size(m->core->sinks), pa_idxset_size(m->core->sources)); diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index d15d9ffa3..a861f6f18 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -1195,7 +1195,7 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname; + char *state_path; pa_sink *sink; pa_source *source; uint32_t idx; @@ -1252,17 +1252,15 @@ int pa__init(pa_module*m) { if (restore_formats) pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_put_hook_callback, u); - if (!(fname = pa_state_path("device-volumes", true))) + if (!(state_path = pa_state_path(NULL, true))) goto fail; - if (!(u->database = pa_database_open(fname, true))) { - pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); - pa_xfree(fname); + if (!(u->database = pa_database_open(state_path, "device-volumes", true, true))) { + pa_xfree(state_path); goto fail; } - pa_log_info("Successfully opened database file '%s'.", fname); - pa_xfree(fname); + pa_xfree(state_path); PA_IDXSET_FOREACH(sink, m->core->sinks, idx) subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c index 4cfe3ed48..f8a9e9514 100644 --- a/src/modules/module-equalizer-sink.c +++ b/src/modules/module-equalizer-sink.c @@ -946,7 +946,7 @@ static void save_state(struct userdata *u) { float *H; pa_datum key, data; pa_database *database; - char *dbname; + char *state_path; char *packed; size_t packed_length; @@ -969,9 +969,9 @@ static void save_state(struct userdata *u) { data.data = state; data.size = filter_state_size + packed_length; //thread safety for 0.9.17? - pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, false)); - pa_assert_se(database = pa_database_open(dbname, true)); - pa_xfree(dbname); + pa_assert_se(state_path = pa_state_path(NULL, false)); + pa_assert_se(database = pa_database_open(state_path, EQ_STATE_DB, false, true)); + pa_xfree(state_path); pa_database_set(database, &key, &data, true); pa_database_sync(database); @@ -1020,10 +1020,10 @@ static void load_state(struct userdata *u) { float *H; pa_datum key, value; pa_database *database; - char *dbname; - pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, false)); - database = pa_database_open(dbname, false); - pa_xfree(dbname); + char *state_path; + pa_assert_se(state_path = pa_state_path(NULL, false)); + database = pa_database_open(state_path, EQ_STATE_DB, false, false); + pa_xfree(state_path); if (!database) { pa_log("No resume state"); return; @@ -1626,12 +1626,12 @@ void dbus_init(struct userdata *u) { sink_list = pa_shared_get(u->sink->core, SINKLIST); u->database = pa_shared_get(u->sink->core, EQDB); if (sink_list == NULL) { - char *dbname; + char *state_path; sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func); pa_shared_set(u->sink->core, SINKLIST, sink_list); - pa_assert_se(dbname = pa_state_path("equalizer-presets", false)); - pa_assert_se(u->database = pa_database_open(dbname, true)); - pa_xfree(dbname); + pa_assert_se(state_path = pa_state_path(NULL, false)); + pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true)); + pa_xfree(state_path); pa_shared_set(u->sink->core, EQDB, u->database); pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core); pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME); diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index f4fed6d64..5726d0818 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -714,6 +714,9 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, p pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + if (!PA_SINK_IS_LINKED(u->sink->state)) + return; + if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE) pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE); else diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 7144a664b..c10310d03 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -2259,7 +2259,7 @@ static void clean_up_db(struct userdata *u) { int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname; + char *state_path; pa_sink_input *si; pa_source_output *so; uint32_t idx; @@ -2317,17 +2317,15 @@ int pa__init(pa_module*m) { pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_fixate_hook_callback, u); } - if (!(fname = pa_state_path("stream-volumes", true))) + if (!(state_path = pa_state_path(NULL, true))) goto fail; - if (!(u->database = pa_database_open(fname, true))) { - pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); - pa_xfree(fname); + if (!(u->database = pa_database_open(state_path, "stream-volumes", true, true))) { + pa_xfree(state_path); goto fail; } - pa_log_info("Successfully opened database file '%s'.", fname); - pa_xfree(fname); + pa_xfree(state_path); clean_up_db(u); diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c index 84b856659..99d61a4b8 100644 --- a/src/modules/module-switch-on-port-available.c +++ b/src/modules/module-switch-on-port-available.c @@ -228,9 +228,7 @@ static struct port_pointers find_port_pointers(pa_device_port *port) { } /* Switches to a port, switching profiles if necessary or preferred */ -static void switch_to_port(pa_device_port *port) { - struct port_pointers pp = find_port_pointers(port); - +static void switch_to_port(pa_device_port *port, struct port_pointers pp) { if (pp.is_port_active) return; /* Already selected */ @@ -252,8 +250,7 @@ static void switch_to_port(pa_device_port *port) { } /* Switches away from a port, switching profiles if necessary or preferred */ -static void switch_from_port(pa_device_port *port) { - struct port_pointers pp = find_port_pointers(port); +static void switch_from_port(pa_device_port *port, struct port_pointers pp) { pa_device_port *p, *best_port = NULL; void *state; @@ -281,13 +278,15 @@ static void switch_from_port(pa_device_port *port) { * profile is still available in the * PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED callback, as at this point * the profile availability hasn't been updated yet. */ - if (best_port) - switch_to_port(best_port); + if (best_port) { + struct port_pointers best_pp = find_port_pointers(best_port); + switch_to_port(best_port, best_pp); + } } static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) { - pa_assert(port); + struct port_pointers pp = find_port_pointers(port); if (!port->card) { pa_log_warn("Port %s does not have a card", port->name); @@ -314,6 +313,15 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port * functionality for setups that can't trigger this kind of * interaction. * + * For headset or microphone, if they are part of some availability group + * and they become unknown from off, it needs to check if their source is + * unlinked or not, if their source is unlinked, let switch_to_port() + * process them, then with the running of pa_card_set_profile(), their + * source will be created, otherwise the headset or microphone can't be used + * to record sound since there is no source for these 2 ports. This issue + * is observed on Dell machines which have multi-function audio jack but no + * internal mic. + * * We should make this configurable so that users can optionally * override the default to a headset or mic. */ @@ -323,20 +331,22 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port break; } - /* For no we only switch the headphone port */ - if (port->direction != PA_DIRECTION_OUTPUT) { + /* Switch the headphone port, the input ports without source and the + * input ports their source->active_port is part of a group of ports. + */ + if (port->direction == PA_DIRECTION_INPUT && pp.source && !pp.source->active_port->availability_group) { pa_log_debug("Not switching to input port %s, its availability is unknown.", port->name); break; } - switch_to_port(port); + switch_to_port(port, pp); break; case PA_AVAILABLE_YES: - switch_to_port(port); + switch_to_port(port, pp); break; case PA_AVAILABLE_NO: - switch_from_port(port); + switch_from_port(port, pp); break; default: break; diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 5205349bd..58f3d1e2c 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -2074,34 +2074,34 @@ int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *b } closedir(d); + if ((count = pa_dynarray_size(files))) { + sorted_files = pa_xnew(char*, count); + for (i = 0; i < count; ++i) + sorted_files[i] = pa_dynarray_get(files, i); + pa_dynarray_free(files); - count = pa_dynarray_size(files); - sorted_files = pa_xnew(char*, count); - for (i = 0; i < count; ++i) - sorted_files[i] = pa_dynarray_get(files, i); - pa_dynarray_free(files); - - for (i = 0; i < count; ++i) { - for (unsigned j = 0; j < count; ++j) { - if (strcmp(sorted_files[i], sorted_files[j]) < 0) { - char *tmp = sorted_files[i]; - sorted_files[i] = sorted_files[j]; - sorted_files[j] = tmp; + for (i = 0; i < count; ++i) { + for (unsigned j = 0; j < count; ++j) { + if (strcmp(sorted_files[i], sorted_files[j]) < 0) { + char *tmp = sorted_files[i]; + sorted_files[i] = sorted_files[j]; + sorted_files[j] = tmp; + } } } - } - for (i = 0; i < count; ++i) { - if (!failed) { - if (pa_cli_command_execute_file(c, sorted_files[i], buf, fail) < 0 && *fail) - failed = true; + for (i = 0; i < count; ++i) { + if (!failed) { + if (pa_cli_command_execute_file(c, sorted_files[i], buf, fail) < 0 && *fail) + failed = true; + } + + pa_xfree(sorted_files[i]); } - - pa_xfree(sorted_files[i]); + pa_xfree(sorted_files); + if (failed) + return -1; } - pa_xfree(sorted_files); - if (failed) - return -1; } } else if (pa_cli_command_execute_file(c, filename, buf, fail) < 0 && *fail) { return -1; diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c index b1da9df9f..b39f7dee2 100644 --- a/src/pulsecore/database-gdbm.c +++ b/src/pulsecore/database-gdbm.c @@ -59,17 +59,16 @@ void pa_datum_free(pa_datum *d) { pa_zero(d); } -pa_database* pa_database_open(const char *fn, bool for_write) { +const char* pa_database_get_filename_suffix(void) { + return ".gdbm"; +} + +pa_database* pa_database_open_internal(const char *path, bool for_write) { GDBM_FILE f; int gdbm_cache_size; - char *path; - pa_assert(fn); + pa_assert(path); - /* We include the host identifier in the file name because gdbm - * files are CPU dependent, and we don't want things to go wrong - * if we are on a multiarch system. */ - path = pa_sprintf_malloc("%s."CANONICAL_HOST".gdbm", fn); errno = 0; /* We need to set the block size explicitly here, since otherwise @@ -80,8 +79,6 @@ pa_database* pa_database_open(const char *fn, bool for_write) { if (f) pa_log_debug("Opened GDBM database '%s'", path); - pa_xfree(path); - if (!f) { if (errno == 0) errno = EIO; diff --git a/src/pulsecore/database-simple.c b/src/pulsecore/database-simple.c index 387648769..96af8e098 100644 --- a/src/pulsecore/database-simple.c +++ b/src/pulsecore/database-simple.c @@ -222,14 +222,16 @@ static int fill_data(simple_data *db, FILE *f) { return pa_hashmap_size(db->map); } -pa_database* pa_database_open(const char *fn, bool for_write) { +const char* pa_database_get_filename_suffix(void) { + return ".simple"; +} + +pa_database* pa_database_open_internal(const char *path, bool for_write) { FILE *f; - char *path; simple_data *db; - pa_assert(fn); + pa_assert(path); - path = pa_sprintf_malloc("%s."CANONICAL_HOST".simple", fn); errno = 0; f = pa_fopen_cloexec(path, "r"); @@ -251,8 +253,6 @@ pa_database* pa_database_open(const char *fn, bool for_write) { db = NULL; } - pa_xfree(path); - return (pa_database*) db; } diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c index 282f58061..6605ed8fc 100644 --- a/src/pulsecore/database-tdb.c +++ b/src/pulsecore/database-tdb.c @@ -97,18 +97,18 @@ finish: return c; } -pa_database* pa_database_open(const char *fn, bool for_write) { +const char* pa_database_get_filename_suffix(void) { + return ".tdb"; +} + +pa_database* pa_database_open_internal(const char *path, bool for_write) { struct tdb_context *c; - char *path; - pa_assert(fn); + pa_assert(path); - path = pa_sprintf_malloc("%s.tdb", fn); if ((c = tdb_open_cloexec(path, 0, TDB_NOSYNC|TDB_NOLOCK, (for_write ? O_RDWR|O_CREAT : O_RDONLY), 0644))) pa_log_debug("Opened TDB database '%s'", path); - pa_xfree(path); - if (!c) { if (errno == 0) errno = EIO; diff --git a/src/pulsecore/database.c b/src/pulsecore/database.c new file mode 100644 index 000000000..8b3b89c56 --- /dev/null +++ b/src/pulsecore/database.c @@ -0,0 +1,107 @@ +/*** + This file is part of PulseAudio. + + Copyright 2020 Igor V. Kovalenko + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include +#include + +#include "database.h" +#include "core-error.h" + +pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write) { + + const char *filename_suffix = pa_database_get_filename_suffix(); + + char *machine_id = NULL, *filename_prefix, *full_path; + + DIR *database_dir = NULL; + struct dirent *de; + + pa_database *f; + + pa_assert(filename_suffix && filename_suffix[0]); + + if (prependmid && !(machine_id = pa_machine_id())) { + return NULL; + } + + /* Database file name starts with ${machine_id}-${fn} */ + if (machine_id) + filename_prefix = pa_sprintf_malloc("%s-%s", machine_id, fn); + else + filename_prefix = pa_xstrdup(fn); + + /* Search for existing database directory entry name matching architecture suffix and filename suffix. */ + database_dir = opendir(path); + + if (database_dir) { + for (;;) { + errno = 0; + de = readdir(database_dir); + if (!de) { + if (errno) { + pa_log_warn("Unable to search for existing database file, readdir() failed: %s", pa_cstrerror(errno)); + /* can continue as if there is no matching database file candidate */ + } + + break; + } + + if (pa_startswith(de->d_name, filename_prefix) + && de->d_name[strlen(filename_prefix)] == '.' + && pa_endswith(de->d_name + strlen(filename_prefix) + 1, filename_suffix)) { + + /* candidate filename found, replace filename_prefix with this one */ + + pa_log_debug("Found existing database file '%s/%s', using it", path, de->d_name); + pa_xfree(filename_prefix); + filename_prefix = pa_xstrndup(de->d_name, strlen(de->d_name) - strlen(filename_suffix)); + break; + } + } + + closedir(database_dir); + } else { + pa_log_warn("Unable to search for existing database file, failed to open directory %s: %s", path, pa_cstrerror(errno)); + } + + full_path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s%s", path, filename_prefix, filename_suffix); + + f = pa_database_open_internal(full_path, for_write); + + if (f) + pa_log_info("Successfully opened '%s' database file '%s'.", fn, full_path); + else + pa_log("Failed to open '%s' database file '%s': %s", fn, full_path, pa_cstrerror(errno)); + + pa_xfree(full_path); + pa_xfree(filename_prefix); + + /* deallocate machine_id if it was used to construct file name */ + pa_xfree(machine_id); + + return f; +} diff --git a/src/pulsecore/database.h b/src/pulsecore/database.h index 3a1c7ceaf..7fa489c67 100644 --- a/src/pulsecore/database.h +++ b/src/pulsecore/database.h @@ -38,8 +38,29 @@ typedef struct pa_datum { void pa_datum_free(pa_datum *d); -/* This will append a suffix to the filename */ -pa_database* pa_database_open(const char *fn, bool for_write); +/* Database implementation; returns non-empty database filename extension string */ +const char* pa_database_get_filename_suffix(void); + +/* Opens a database file. The file is loaded from the directory indicated by + * path. The file name is constructed by using fn as the base and then adding + * several parts: + * 1) If prependmid is true, the machine id is prepended to the file name. + * 2) The database implementation specific suffix is added. + * 3) Older versions of PulseAudio in some cases added the CPU architecture + * to the file name, which was later deemed unnecessary, but for + * compatibility reasons we still need to look for those files, so we scan + * the directory for files that match the prefix (possible machine id plus + * fn) and the suffix, and if any matches are found, we use the first one. + * + * When no existing file is found, we create a new file for the database + * (without the CPU architecture part in the name). + * + * For a read-only database, set for_write to false. */ + +pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write); + +/* Database implementation; opens specified database file using provided path. */ +pa_database* pa_database_open_internal(const char *path, bool for_write); void pa_database_close(pa_database *db); pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data); diff --git a/src/pulsecore/meson.build b/src/pulsecore/meson.build index 5f78be012..99a702ea1 100644 --- a/src/pulsecore/meson.build +++ b/src/pulsecore/meson.build @@ -14,6 +14,7 @@ libpulsecore_sources = [ 'cpu-orc.c', 'cpu-x86.c', 'device-port.c', + 'database.c', 'ffmpeg/resample2.c', 'filter/biquad.c', 'filter/crossover.c',