From 4dfe6a8fa468d8534d684fa64c7c3919a5213422 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Apr 10 2018 05:31:28 +0000 Subject: import pulseaudio-10.0-5.el7 --- diff --git a/SOURCES/0001-alsa-mixer-Add-support-for-usb-audio-in-the-Dell-doc.patch b/SOURCES/0001-alsa-mixer-Add-support-for-usb-audio-in-the-Dell-doc.patch new file mode 100644 index 0000000..d5bffcd --- /dev/null +++ b/SOURCES/0001-alsa-mixer-Add-support-for-usb-audio-in-the-Dell-doc.patch @@ -0,0 +1,96 @@ +From 1e057be093d948faa26b94b6efcae79bee7e6aee Mon Sep 17 00:00:00 2001 +From: Hui Wang +Date: Fri, 26 May 2017 15:42:40 +0800 +Subject: [PATCH 1/4] alsa-mixer: Add support for usb audio in the Dell dock + TB16 + +There are one headset jack on the front panel of TB16, through this +jack, we have one stereo headphone output (hw:%f,0,0) and one mono +headset-mic input (hw:%f,0,0); and there is one speaker output jack +(hw:%f,1,0) on the rear panel of TB16. + +The detail information of the Dell dock TB16: +http://www.dell.com/support/article/sg/en/sgbsdt1/SLN301105 + +Signed-off-by: Hui Wang +--- + .../alsa/mixer/profile-sets/90-pulseaudio.rules | 1 + + .../profile-sets/dell-dock-tb16-usb-audio.conf | 55 ++++++++++++++++++++++ + 3 files changed, 57 insertions(+) + create mode 100644 src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf + +diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules +index 70e34e6fa..805a05b2a 100644 +--- a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules ++++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules +@@ -98,5 +98,6 @@ ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="nativ + ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf" + ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf" + ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf" ++ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" + + LABEL="pulseaudio_end" +diff --git a/src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf +new file mode 100644 +index 000000000..118655246 +--- /dev/null ++++ b/src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf +@@ -0,0 +1,55 @@ ++# This file is part of PulseAudio. ++# ++# 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 ++# 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 . ++ ++; Dell Dock TB16 USB audio ++; ++; This card has two stereo pairs of output, One Mono input. ++; ++; See default.conf for an explanation on the directives used here. ++ ++[General] ++auto-profiles = no ++ ++[Mapping analog-stereo-headphone] ++description = Headphone ++device-strings = hw:%f,0,0 ++channel-map = left,right ++direction = output ++ ++[Mapping analog-stereo-speaker] ++description = Speaker ++device-strings = hw:%f,1,0 ++channel-map = left,right ++direction = output ++ ++[Mapping analog-stereo-mic] ++description = Headset-Mic ++device-strings = hw:%f,0,0 ++channel-map = left,right ++direction = input ++ ++ ++[Profile output:analog-stereo-speaker] ++description = Speaker ++output-mappings = analog-stereo-speaker ++priority = 60 ++skip-probe = yes ++ ++[Profile output:analog-stereo-headphone+input:analog-stereo-mic] ++description = Headset ++output-mappings = analog-stereo-headphone ++input-mappings = analog-stereo-mic ++priority = 80 ++skip-probe = yes +-- +2.14.3 + diff --git a/SOURCES/0002-sink-source-update-the-default-sink-source-on-port-s.patch b/SOURCES/0002-sink-source-update-the-default-sink-source-on-port-s.patch new file mode 100644 index 0000000..2a2a5ec --- /dev/null +++ b/SOURCES/0002-sink-source-update-the-default-sink-source-on-port-s.patch @@ -0,0 +1,60 @@ +From 1e13b722545a23699479bb177648f30f7be396ca Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Sun, 7 May 2017 12:12:39 +0300 +Subject: [PATCH 2/4] sink, source: update the default sink/source on port + switches + +When sinks are compared during the default sink selection, the active +port's availability is inspected. Therefore, the default sink should be +updated when the active port changes, because the new port may have +different availability status than the old port. + +For example, let's say that a laptop has an analog sink with a speaker +and a headphone port, and headphones are initially plugged in, so both +ports can be used[1]. The headphone port is initially the active port. +There's also a null sink in the system. When the headphones are +unplugged, the headphone port becomes unavailable, and the null sink +becomes the new default sink. Then module-switch-on-connect changes the +analog sink port to speakers. Now the default sink should change back to +the analog sink, but that doesn't happen without this patch. + +[1] Actually we currently mark speakers as unavailable when headphones +are plugged in, but that's not strictly necessary. My example relies on +both ports being available initially, so the bug can't be reproduced +with the current mixer configuration. +--- + src/pulsecore/sink.c | 3 +++ + src/pulsecore/source.c | 3 +++ + 2 files changed, 6 insertions(+) + +diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c +index aa2182220..ebb13c2c9 100644 +--- a/src/pulsecore/sink.c ++++ b/src/pulsecore/sink.c +@@ -3355,6 +3355,9 @@ int pa_sink_set_port(pa_sink *s, const char *name, bool save) { + + pa_sink_set_port_latency_offset(s, s->active_port->latency_offset); + ++ /* The active port affects the default sink selection. */ ++ pa_core_update_default_sink(s->core); ++ + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s); + + return 0; +diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c +index 8ce781804..3f0f53b52 100644 +--- a/src/pulsecore/source.c ++++ b/src/pulsecore/source.c +@@ -2630,6 +2630,9 @@ int pa_source_set_port(pa_source *s, const char *name, bool save) { + s->active_port = port; + s->save_port = save; + ++ /* The active port affects the default source selection. */ ++ pa_core_update_default_source(s->core); ++ + pa_source_set_port_latency_offset(s, s->active_port->latency_offset); + + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], s); +-- +2.14.3 + diff --git a/SOURCES/0003-improve-default-sink-source-handling.patch b/SOURCES/0003-improve-default-sink-source-handling.patch new file mode 100644 index 0000000..c39ff6a --- /dev/null +++ b/SOURCES/0003-improve-default-sink-source-handling.patch @@ -0,0 +1,1262 @@ +From 84dbc69202771252b0c7a0d8b6b32f728931bb7f Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Thu, 16 Feb 2017 12:09:38 +0200 +Subject: [PATCH 3/4] improve default sink/source handling + +Currently the default sink policy is simple: either the user has +configured it explicitly, in which case we always use that as the +default, or we pick the sink with the highest priority. The sink +priorities are currently static, so there's no need to worry about +updating the default sink when sink priorities change. + +I intend to make things a bit more complex: if the active port of a sink +is unavailable, the sink should not be the default sink, and I also want +to make sink priorities dependent on the active port, so changing the +port should cause re-evaluation of which sink to choose as the default. +Currently the default sink choice is done only when someone calls +pa_namereg_get_default_sink(), and change notifications are only sent +when a sink is created or destroyed. That makes it hard to add new rules +to the default sink selection policy. + +This patch moves the default sink selection to +pa_core_update_default_sink(), which is called whenever something +happens that can affect the default sink choice. That function needs to +know the previous choice in order to send change notifications as +appropriate, but previously pa_core.default_sink was only set when the +user had configured it explicitly. Now pa_core.default_sink is always +set (unless there are no sinks at all), so pa_core_update_default_sink() +can use that to get the previous choice. The user configuration is saved +in a new variable, pa_core.configured_default_sink. + +pa_namereg_get_default_sink() is now unnecessary, because +pa_core.default_sink can be used directly to get the +currently-considered-best sink. pa_namereg_set_default_sink() is +replaced by pa_core_set_configured_default_sink(). + +I haven't confirmed it, but I expect that this patch will fix problems +in the D-Bus protocol related to default sink handling. The D-Bus +protocol used to get confused when the current default sink gets +removed. It would incorrectly think that if there's no explicitly +configured default sink, then there's no default sink at all. Even +worse, when the D-Bus thinks that there's no default sink, it concludes +that there are no sinks at all, which made it impossible to configure +the default sink via the D-Bus interface. Now that pa_core.default_sink +is always set, except when there really aren't any sinks, the D-Bus +protocol should behave correctly. + +BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=99425 +--- + src/modules/dbus/iface-core.c | 114 +++++++++++++------ + src/modules/dbus/iface-sample.c | 10 +- + src/modules/module-default-device-restore.c | 14 +-- + src/modules/module-intended-roles.c | 37 +++--- + src/modules/module-rescue-streams.c | 20 ++-- + src/modules/module-switch-on-connect.c | 30 ++--- + src/pulsecore/cli-command.c | 20 ++-- + src/pulsecore/cli-text.c | 12 +- + src/pulsecore/core.c | 170 ++++++++++++++++++++++++++++ + src/pulsecore/core.h | 28 ++++- + src/pulsecore/namereg.c | 115 +------------------ + src/pulsecore/namereg.h | 6 - + src/pulsecore/protocol-native.c | 19 ++-- + src/pulsecore/sink.c | 7 ++ + src/pulsecore/source.c | 7 ++ + 15 files changed, 370 insertions(+), 239 deletions(-) + +diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c +index 508913de1..3f368ab46 100644 +--- a/src/modules/dbus/iface-core.c ++++ b/src/modules/dbus/iface-core.c +@@ -721,7 +721,7 @@ static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBu + return; + } + +- pa_namereg_set_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink)); ++ pa_core_set_configured_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink)); + + pa_dbus_send_empty_reply(conn, msg); + } +@@ -809,7 +809,7 @@ static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, D + return; + } + +- pa_namereg_set_default_source(c->core, pa_dbusiface_device_get_source(fallback_source)); ++ pa_core_set_configured_default_source(c->core, pa_dbusiface_device_get_source(fallback_source)); + + pa_dbus_send_empty_reply(conn, msg); + } +@@ -1692,6 +1692,28 @@ static pa_hook_result_t sample_cache_removed_cb(void *hook_data, void *call_data + return PA_HOOK_OK; + } + ++static pa_dbusiface_device *create_dbus_object_for_sink(pa_dbusiface_core *c, pa_sink *s) { ++ pa_dbusiface_device *d; ++ const char *object_path; ++ DBusMessage *signal_msg; ++ ++ d = pa_dbusiface_device_new_sink(c, s); ++ object_path = pa_dbusiface_device_get_path(d); ++ ++ pa_assert_se(pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(s->index), d) >= 0); ++ pa_assert_se(pa_hashmap_put(c->sinks_by_path, (char *) object_path, d) >= 0); ++ ++ pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, ++ PA_DBUS_CORE_INTERFACE, ++ signals[SIGNAL_NEW_SINK].name))); ++ pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); ++ ++ pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); ++ dbus_message_unref(signal_msg); ++ ++ return d; ++} ++ + static pa_hook_result_t default_sink_changed_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_sink *new_fallback_sink = call_data; +@@ -1707,7 +1729,15 @@ static pa_hook_result_t default_sink_changed_cb(void *hook_data, void *call_data + c->fallback_sink = new_fallback_sink ? pa_sink_ref(new_fallback_sink) : NULL; + + if (c->fallback_sink) { +- pa_assert_se((device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)))); ++ device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)); ++ ++ /* It's possible that we haven't created a dbus object for the ++ * source yet, because if a new source immediately becomes the ++ * default source, the default source change hook is fired before ++ * the put hook. */ ++ if (!device_iface) ++ device_iface = create_dbus_object_for_sink(c, c->fallback_sink); ++ + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, +@@ -1730,6 +1760,28 @@ static pa_hook_result_t default_sink_changed_cb(void *hook_data, void *call_data + return PA_HOOK_OK; + } + ++static pa_dbusiface_device *create_dbus_object_for_source(pa_dbusiface_core *c, pa_source *s) { ++ pa_dbusiface_device *d; ++ const char *object_path; ++ DBusMessage *signal_msg; ++ ++ d = pa_dbusiface_device_new_source(c, s); ++ object_path = pa_dbusiface_device_get_path(d); ++ ++ pa_assert_se(pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(s->index), d) >= 0); ++ pa_assert_se(pa_hashmap_put(c->sources_by_path, (char *) object_path, d) >= 0); ++ ++ pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, ++ PA_DBUS_CORE_INTERFACE, ++ signals[SIGNAL_NEW_SOURCE].name))); ++ pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); ++ ++ pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); ++ dbus_message_unref(signal_msg); ++ ++ return d; ++} ++ + static pa_hook_result_t default_source_changed_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_source *new_fallback_source = call_data; +@@ -1745,7 +1797,15 @@ static pa_hook_result_t default_source_changed_cb(void *hook_data, void *call_da + c->fallback_source = new_fallback_source ? pa_source_ref(new_fallback_source) : NULL; + + if (c->fallback_source) { +- pa_assert_se((device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index)))); ++ device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index)); ++ ++ /* It's possible that we haven't created a dbus object for the ++ * source yet, because if a new source immediately becomes the ++ * default source, the default source change hook is fired before ++ * the put hook. */ ++ if (!device_iface) ++ device_iface = create_dbus_object_for_source(c, c->fallback_source); ++ + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, +@@ -1983,26 +2043,17 @@ static pa_hook_result_t client_unlink_cb(void *hook_data, void *call_data, void + static pa_hook_result_t sink_put_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_sink *s = call_data; +- pa_dbusiface_device *d = NULL; +- const char *object_path = NULL; +- DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + +- d = pa_dbusiface_device_new_sink(c, s); +- object_path = pa_dbusiface_device_get_path(d); +- +- pa_assert_se(pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(s->index), d) >= 0); +- pa_assert_se(pa_hashmap_put(c->sinks_by_path, (char *) object_path, d) >= 0); +- +- pa_assert_se(signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, +- PA_DBUS_CORE_INTERFACE, +- signals[SIGNAL_NEW_SINK].name)); +- pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); ++ /* We may have alredy encountered this sink, because if the new sink was ++ * chosen as the default sink, the default sink change hook was fired ++ * first, and we saw the sink in default_sink_changed_cb(). */ ++ if (pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(s->index))) ++ return PA_HOOK_OK; + +- pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); +- dbus_message_unref(signal_msg); ++ create_dbus_object_for_sink(c, s); + + return PA_HOOK_OK; + } +@@ -2037,26 +2088,17 @@ static pa_hook_result_t sink_unlink_cb(void *hook_data, void *call_data, void *s + static pa_hook_result_t source_put_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_source *s = call_data; +- pa_dbusiface_device *d = NULL; +- const char *object_path = NULL; +- DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + +- d = pa_dbusiface_device_new_source(c, s); +- object_path = pa_dbusiface_device_get_path(d); +- +- pa_assert_se(pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(s->index), d) >= 0); +- pa_assert_se(pa_hashmap_put(c->sources_by_path, (char *) object_path, d) >= 0); +- +- pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, +- PA_DBUS_CORE_INTERFACE, +- signals[SIGNAL_NEW_SOURCE].name))); +- pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); ++ /* We may have alredy encountered this source, because if the new source ++ * was chosen as the default source, the default source change hook was ++ * fired first, and we saw the source in default_source_changed_cb(). */ ++ if (pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(s->index))) ++ return PA_HOOK_OK; + +- pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); +- dbus_message_unref(signal_msg); ++ create_dbus_object_for_source(c, s); + + return PA_HOOK_OK; + } +@@ -2158,8 +2200,8 @@ pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core) { + c->samples = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) pa_dbusiface_sample_free); + c->modules = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) pa_dbusiface_module_free); + c->clients = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) pa_dbusiface_client_free); +- c->fallback_sink = pa_namereg_get_default_sink(core); +- c->fallback_source = pa_namereg_get_default_source(core); ++ c->fallback_sink = core->default_sink; ++ c->fallback_source = core->default_source; + c->default_sink_changed_slot = pa_hook_connect(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], + PA_HOOK_NORMAL, default_sink_changed_cb, c); + c->default_source_changed_slot = pa_hook_connect(&core->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], +diff --git a/src/modules/dbus/iface-sample.c b/src/modules/dbus/iface-sample.c +index 61f2ba091..51189196d 100644 +--- a/src/modules/dbus/iface-sample.c ++++ b/src/modules/dbus/iface-sample.c +@@ -352,7 +352,6 @@ static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata) + DBusMessageIter msg_iter; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; +- pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); +@@ -370,13 +369,18 @@ static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata) + goto finish; + } + +- if (!(sink = pa_namereg_get_default_sink(s->sample->core))) { ++ if (!s->sample->core->default_sink) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Can't play sample %s, because there are no sinks available.", s->sample->name); + goto finish; + } + +- if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { ++ if (pa_scache_play_item(s->sample->core, ++ s->sample->name, ++ s->sample->core->default_sink, ++ volume, ++ property_list, ++ NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } +diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c +index d76e28e68..56fee67f8 100644 +--- a/src/modules/module-default-device-restore.c ++++ b/src/modules/module-default-device-restore.c +@@ -56,7 +56,7 @@ static void load(struct userdata *u) { + + /* We never overwrite manually configured settings */ + +- if (u->core->default_sink) ++ if (u->core->configured_default_sink) + pa_log_info("Manually configured default sink, not overwriting."); + else if ((f = pa_fopen_cloexec(u->sink_filename, "r"))) { + char ln[256] = ""; +@@ -69,7 +69,7 @@ static void load(struct userdata *u) { + if (!ln[0]) + pa_log_info("No previous default sink setting, ignoring."); + else if ((s = pa_namereg_get(u->core, ln, PA_NAMEREG_SINK))) { +- pa_namereg_set_default_sink(u->core, s); ++ pa_core_set_configured_default_sink(u->core, s); + pa_log_info("Restored default sink '%s'.", ln); + } else + pa_log_info("Saved default sink '%s' not existent, not restoring default sink setting.", ln); +@@ -77,7 +77,7 @@ static void load(struct userdata *u) { + } else if (errno != ENOENT) + pa_log("Failed to load default sink: %s", pa_cstrerror(errno)); + +- if (u->core->default_source) ++ if (u->core->configured_default_source) + pa_log_info("Manually configured default source, not overwriting."); + else if ((f = pa_fopen_cloexec(u->source_filename, "r"))) { + char ln[256] = ""; +@@ -90,7 +90,7 @@ static void load(struct userdata *u) { + if (!ln[0]) + pa_log_info("No previous default source setting, ignoring."); + else if ((s = pa_namereg_get(u->core, ln, PA_NAMEREG_SOURCE))) { +- pa_namereg_set_default_source(u->core, s); ++ pa_core_set_configured_default_source(u->core, s); + pa_log_info("Restored default source '%s'.", ln); + } else + pa_log_info("Saved default source '%s' not existent, not restoring default source setting.", ln); +@@ -107,8 +107,7 @@ static void save(struct userdata *u) { + + if (u->sink_filename) { + if ((f = pa_fopen_cloexec(u->sink_filename, "w"))) { +- pa_sink *s = pa_namereg_get_default_sink(u->core); +- fprintf(f, "%s\n", s ? s->name : ""); ++ fprintf(f, "%s\n", u->core->default_sink ? u->core->default_sink->name : ""); + fclose(f); + } else + pa_log("Failed to save default sink: %s", pa_cstrerror(errno)); +@@ -116,8 +115,7 @@ static void save(struct userdata *u) { + + if (u->source_filename) { + if ((f = pa_fopen_cloexec(u->source_filename, "w"))) { +- pa_source *s = pa_namereg_get_default_source(u->core); +- fprintf(f, "%s\n", s ? s->name : ""); ++ fprintf(f, "%s\n", u->core->default_source ? u->core->default_source->name : ""); + fclose(f); + } else + pa_log("Failed to save default source: %s", pa_cstrerror(errno)); +diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c +index 60ec7e982..f906b8800 100644 +--- a/src/modules/module-intended-roles.c ++++ b/src/modules/module-intended-roles.c +@@ -69,7 +69,7 @@ static bool role_match(pa_proplist *proplist, const char *role) { + + static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { + const char *role; +- pa_sink *s, *def; ++ pa_sink *s; + uint32_t idx; + + pa_assert(c); +@@ -92,13 +92,13 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n + } + + /* Prefer the default sink over any other sink, just in case... */ +- if ((def = pa_namereg_get_default_sink(c))) +- if (role_match(def->proplist, role) && pa_sink_input_new_data_set_sink(new_data, def, false)) ++ if (c->default_sink) ++ if (role_match(c->default_sink->proplist, role) && pa_sink_input_new_data_set_sink(new_data, c->default_sink, false)) + return PA_HOOK_OK; + + /* @todo: favour the highest priority device, not the first one we find? */ + PA_IDXSET_FOREACH(s, c->sinks, idx) { +- if (s == def) ++ if (s == c->default_sink) + continue; + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s))) +@@ -113,7 +113,7 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n + + static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + const char *role; +- pa_source *s, *def; ++ pa_source *s; + uint32_t idx; + + pa_assert(c); +@@ -136,9 +136,9 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou + } + + /* Prefer the default source over any other source, just in case... */ +- if ((def = pa_namereg_get_default_source(c))) +- if (role_match(def->proplist, role)) { +- pa_source_output_new_data_set_source(new_data, def, false); ++ if (c->default_source) ++ if (role_match(c->default_source->proplist, role)) { ++ pa_source_output_new_data_set_source(new_data, c->default_source, false); + return PA_HOOK_OK; + } + +@@ -146,7 +146,7 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou + if (s->monitor_of) + continue; + +- if (s == def) ++ if (s == c->default_source) + continue; + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s))) +@@ -259,7 +259,6 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, + static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_sink_input *si; + uint32_t idx; +- pa_sink *def; + + pa_assert(c); + pa_assert(sink); +@@ -271,7 +270,7 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str + return PA_HOOK_OK; + + /* If there not default sink, then there is no sink at all */ +- if (!(def = pa_namereg_get_default_sink(c))) ++ if (!c->default_sink) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(si, sink->inputs, idx) { +@@ -286,14 +285,14 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str + continue; + + /* Would the default sink fit? If so, let's use it */ +- if (def != sink && role_match(def->proplist, role)) +- if (pa_sink_input_move_to(si, def, false) >= 0) ++ if (c->default_sink != sink && role_match(c->default_sink->proplist, role)) ++ if (pa_sink_input_move_to(si, c->default_sink, false) >= 0) + continue; + + /* Try to find some other fitting sink */ + /* @todo: favour the highest priority device, not the first one we find? */ + PA_IDXSET_FOREACH(d, c->sinks, jdx) { +- if (d == def || d == sink) ++ if (d == c->default_sink || d == sink) + continue; + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(d))) +@@ -311,7 +310,6 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str + static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_source_output *so; + uint32_t idx; +- pa_source *def; + + pa_assert(c); + pa_assert(source); +@@ -323,7 +321,7 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc + return PA_HOOK_OK; + + /* If there not default source, then there is no source at all */ +- if (!(def = pa_namereg_get_default_source(c))) ++ if (!c->default_source) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(so, source->outputs, idx) { +@@ -341,15 +339,16 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc + continue; + + /* Would the default source fit? If so, let's use it */ +- if (def != source && role_match(def->proplist, role) && !source->monitor_of == !def->monitor_of) { +- pa_source_output_move_to(so, def, false); ++ if (c->default_source != source && role_match(c->default_source->proplist, role) ++ && !source->monitor_of == !c->default_source->monitor_of) { ++ pa_source_output_move_to(so, c->default_source, false); + continue; + } + + /* Try to find some other fitting source */ + /* @todo: favour the highest priority device, not the first one we find? */ + PA_IDXSET_FOREACH(d, c->sources, jdx) { +- if (d == def || d == source) ++ if (d == c->default_source || d == source) + continue; + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(d))) +diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c +index 60ac1c4cf..d3c9953ec 100644 +--- a/src/modules/module-rescue-streams.c ++++ b/src/modules/module-rescue-streams.c +@@ -96,7 +96,7 @@ static void build_group_ports(pa_hashmap *g_ports, pa_hashmap *s_ports) { + } + + static pa_sink* find_evacuation_sink(pa_core *c, pa_sink_input *i, pa_sink *skip) { +- pa_sink *target, *def, *fb_sink = NULL; ++ pa_sink *target, *fb_sink = NULL; + uint32_t idx; + pa_hashmap *all_ports; + pa_device_port *best_port; +@@ -104,15 +104,13 @@ static pa_sink* find_evacuation_sink(pa_core *c, pa_sink_input *i, pa_sink *skip + pa_assert(c); + pa_assert(i); + +- def = pa_namereg_get_default_sink(c); +- +- if (def && def != skip && pa_sink_input_may_move_to(i, def)) +- return def; ++ if (c->default_sink && c->default_sink != skip && pa_sink_input_may_move_to(i, c->default_sink)) ++ return c->default_sink; + + all_ports = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + PA_IDXSET_FOREACH(target, c->sinks, idx) { +- if (target == def) ++ if (target == c->default_sink) + continue; + + if (target == skip) +@@ -204,7 +202,7 @@ static pa_hook_result_t sink_input_move_fail_hook_callback(pa_core *c, pa_sink_i + } + + static pa_source* find_evacuation_source(pa_core *c, pa_source_output *o, pa_source *skip) { +- pa_source *target, *def, *fb_source = NULL; ++ pa_source *target, *fb_source = NULL; + uint32_t idx; + pa_hashmap *all_ports; + pa_device_port *best_port; +@@ -212,15 +210,13 @@ static pa_source* find_evacuation_source(pa_core *c, pa_source_output *o, pa_sou + pa_assert(c); + pa_assert(o); + +- def = pa_namereg_get_default_source(c); +- +- if (def && def != skip && pa_source_output_may_move_to(o, def)) +- return def; ++ if (c->default_source && c->default_source != skip && pa_source_output_may_move_to(o, c->default_source)) ++ return c->default_source; + + all_ports = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + PA_IDXSET_FOREACH(target, c->sources, idx) { +- if (target == def) ++ if (target == c->default_source) + continue; + + if (target == skip) +diff --git a/src/modules/module-switch-on-connect.c b/src/modules/module-switch-on-connect.c +index c844add6e..776c923ee 100644 +--- a/src/modules/module-switch-on-connect.c ++++ b/src/modules/module-switch-on-connect.c +@@ -55,7 +55,7 @@ struct userdata { + static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + pa_sink_input *i; + uint32_t idx; +- pa_sink *def; ++ pa_sink *old_default_sink; + const char *s; + struct userdata *u = userdata; + +@@ -75,24 +75,25 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void* + return PA_HOOK_OK; + } + +- def = pa_namereg_get_default_sink(c); +- if (def == sink) ++ if (c->default_sink == sink) + return PA_HOOK_OK; + + if (u->only_from_unavailable) +- if (!def->active_port || def->active_port->available != PA_AVAILABLE_NO) ++ if (!c->default_sink->active_port || c->default_sink->active_port->available != PA_AVAILABLE_NO) + return PA_HOOK_OK; + ++ old_default_sink = c->default_sink; ++ + /* Actually do the switch to the new sink */ +- pa_namereg_set_default_sink(c, sink); ++ pa_core_set_configured_default_sink(c, sink); + + /* Now move all old inputs over */ +- if (pa_idxset_size(def->inputs) <= 0) { ++ if (pa_idxset_size(old_default_sink->inputs) <= 0) { + pa_log_debug("No sink inputs to move away."); + return PA_HOOK_OK; + } + +- PA_IDXSET_FOREACH(i, def->inputs, idx) { ++ PA_IDXSET_FOREACH(i, old_default_sink->inputs, idx) { + if (i->save_sink || !PA_SINK_INPUT_IS_LINKED(i->state)) + continue; + +@@ -110,7 +111,7 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void* + static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void* userdata) { + pa_source_output *o; + uint32_t idx; +- pa_source *def; ++ pa_source *old_default_source; + const char *s; + struct userdata *u = userdata; + +@@ -134,24 +135,25 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, + return PA_HOOK_OK; + } + +- def = pa_namereg_get_default_source(c); +- if (def == source) ++ if (c->default_source == source) + return PA_HOOK_OK; + + if (u->only_from_unavailable) +- if (!def->active_port || def->active_port->available != PA_AVAILABLE_NO) ++ if (!c->default_source->active_port || c->default_source->active_port->available != PA_AVAILABLE_NO) + return PA_HOOK_OK; + ++ old_default_source = c->default_source; ++ + /* Actually do the switch to the new source */ +- pa_namereg_set_default_source(c, source); ++ pa_core_set_configured_default_source(c, source); + + /* Now move all old outputs over */ +- if (pa_idxset_size(def->outputs) <= 0) { ++ if (pa_idxset_size(old_default_source->outputs) <= 0) { + pa_log_debug("No source outputs to move away."); + return PA_HOOK_OK; + } + +- PA_IDXSET_FOREACH(o, def->outputs, idx) { ++ PA_IDXSET_FOREACH(o, old_default_source->outputs, idx) { + if (o->save_source || !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + continue; + +diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c +index 9a7360582..5a632be45 100644 +--- a/src/pulsecore/cli-command.c ++++ b/src/pulsecore/cli-command.c +@@ -344,8 +344,6 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool + char bytes[PA_BYTES_SNPRINT_MAX]; + const pa_mempool_stat *mstat; + unsigned k; +- pa_sink *def_sink; +- pa_source *def_source; + + static const char* const type_table[PA_MEMBLOCK_TYPE_MAX] = { + [PA_MEMBLOCK_POOL] = "POOL", +@@ -388,12 +386,10 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool + pa_strbuf_printf(buf, "Default channel map: %s\n", + pa_channel_map_snprint(cm, sizeof(cm), &c->default_channel_map)); + +- def_sink = pa_namereg_get_default_sink(c); +- def_source = pa_namereg_get_default_source(c); + pa_strbuf_printf(buf, "Default sink name: %s\n" + "Default source name: %s\n", +- def_sink ? def_sink->name : "none", +- def_source ? def_source->name : "none"); ++ c->default_sink ? c->default_sink->name : "none", ++ c->default_source ? c->default_source->name : "none"); + + for (k = 0; k < PA_MEMBLOCK_TYPE_MAX; k++) + pa_strbuf_printf(buf, +@@ -1034,7 +1030,7 @@ static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *b + } + + if ((s = pa_namereg_get(c, n, PA_NAMEREG_SINK))) +- pa_namereg_set_default_sink(c, s); ++ pa_core_set_configured_default_sink(c, s); + else + pa_strbuf_printf(buf, "Sink %s does not exist.\n", n); + +@@ -1056,7 +1052,7 @@ static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf + } + + if ((s = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) +- pa_namereg_set_default_source(c, s); ++ pa_core_set_configured_default_source(c, s); + else + pa_strbuf_printf(buf, "Source %s does not exist.\n", n); + return 0; +@@ -1850,20 +1846,20 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool + } + + nl = false; +- if ((sink = pa_namereg_get_default_sink(c))) { ++ if (c->default_sink) { + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = true; + } + +- pa_strbuf_printf(buf, "set-default-sink %s\n", sink->name); ++ pa_strbuf_printf(buf, "set-default-sink %s\n", c->default_sink->name); + } + +- if ((source = pa_namereg_get_default_source(c))) { ++ if (c->default_source) { + if (!nl) + pa_strbuf_puts(buf, "\n"); + +- pa_strbuf_printf(buf, "set-default-source %s\n", source->name); ++ pa_strbuf_printf(buf, "set-default-source %s\n", c->default_source->name); + } + + pa_strbuf_puts(buf, "\n### EOF\n"); +diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c +index af79a1ebe..ded82f6e4 100644 +--- a/src/pulsecore/cli-text.c ++++ b/src/pulsecore/cli-text.c +@@ -232,7 +232,7 @@ static const char *source_state_to_string(pa_source_state_t state) { + + char *pa_sink_list_to_string(pa_core *c) { + pa_strbuf *s; +- pa_sink *sink, *default_sink; ++ pa_sink *sink; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + +@@ -240,8 +240,6 @@ char *pa_sink_list_to_string(pa_core *c) { + + pa_strbuf_printf(s, "%u sink(s) available.\n", pa_idxset_size(c->sinks)); + +- default_sink = pa_namereg_get_default_sink(c); +- + PA_IDXSET_FOREACH(sink, c->sinks, idx) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], + cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], +@@ -273,7 +271,7 @@ char *pa_sink_list_to_string(pa_core *c) { + "\tchannel map: %s%s%s\n" + "\tused by: %u\n" + "\tlinked by: %u\n", +- sink == default_sink ? '*' : ' ', ++ sink == c->default_sink ? '*' : ' ', + sink->index, + sink->name, + sink->driver, +@@ -350,7 +348,7 @@ char *pa_sink_list_to_string(pa_core *c) { + + char *pa_source_list_to_string(pa_core *c) { + pa_strbuf *s; +- pa_source *source, *default_source; ++ pa_source *source; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + +@@ -358,8 +356,6 @@ char *pa_source_list_to_string(pa_core *c) { + + pa_strbuf_printf(s, "%u source(s) available.\n", pa_idxset_size(c->sources)); + +- default_source = pa_namereg_get_default_source(c); +- + PA_IDXSET_FOREACH(source, c->sources, idx) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], + cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], +@@ -389,7 +385,7 @@ char *pa_source_list_to_string(pa_core *c) { + "\tchannel map: %s%s%s\n" + "\tused by: %u\n" + "\tlinked by: %u\n", +- source == default_source ? '*' : ' ', ++ source == c->default_source ? '*' : ' ', + source->index, + source->name, + source->driver, +diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c +index 2a96dfa5e..da1abb3ba 100644 +--- a/src/pulsecore/core.c ++++ b/src/pulsecore/core.c +@@ -223,6 +223,176 @@ static void core_free(pa_object *o) { + pa_xfree(c); + } + ++void pa_core_set_configured_default_sink(pa_core *core, pa_sink *sink) { ++ pa_sink *old_sink; ++ ++ pa_assert(core); ++ ++ old_sink = core->configured_default_sink; ++ ++ if (sink == old_sink) ++ return; ++ ++ core->configured_default_sink = sink; ++ pa_log_info("configured_default_sink: %s -> %s", ++ old_sink ? old_sink->name : "(unset)", sink ? sink->name : "(unset)"); ++ ++ pa_core_update_default_sink(core); ++} ++ ++void pa_core_set_configured_default_source(pa_core *core, pa_source *source) { ++ pa_source *old_source; ++ ++ pa_assert(core); ++ ++ old_source = core->configured_default_source; ++ ++ if (source == old_source) ++ return; ++ ++ core->configured_default_source = source; ++ pa_log_info("configured_default_source: %s -> %s", ++ old_source ? old_source->name : "(unset)", source ? source->name : "(unset)"); ++ ++ pa_core_update_default_source(core); ++} ++ ++/* a < b -> return -1 ++ * a == b -> return 0 ++ * a > b -> return 1 */ ++static int compare_sinks(pa_sink *a, pa_sink *b) { ++ pa_core *core; ++ ++ core = a->core; ++ ++ /* The configured default sink is preferred over any other sink. */ ++ if (b == core->configured_default_sink) ++ return -1; ++ if (a == core->configured_default_sink) ++ return 1; ++ ++ if (a->priority < b->priority) ++ return -1; ++ if (a->priority > b->priority) ++ return 1; ++ ++ /* It's hard to find any difference between these sinks, but maybe one of ++ * them is already the default sink? If so, it's best to keep it as the ++ * default to avoid changing the routing for no good reason. */ ++ if (b == core->default_sink) ++ return -1; ++ if (a == core->default_sink) ++ return 1; ++ ++ return 0; ++} ++ ++void pa_core_update_default_sink(pa_core *core) { ++ pa_sink *best = NULL; ++ pa_sink *sink; ++ uint32_t idx; ++ pa_sink *old_default_sink; ++ ++ pa_assert(core); ++ ++ PA_IDXSET_FOREACH(sink, core->sinks, idx) { ++ if (!best) { ++ best = sink; ++ continue; ++ } ++ ++ if (compare_sinks(sink, best) > 0) ++ best = sink; ++ } ++ ++ old_default_sink = core->default_sink; ++ ++ if (best == old_default_sink) ++ return; ++ ++ core->default_sink = best; ++ pa_log_info("default_sink: %s -> %s", ++ old_default_sink ? old_default_sink->name : "(unset)", best ? best->name : "(unset)"); ++ ++ /* If the default sink changed, it may be that the default source has to be ++ * changed too, because monitor sources are prioritized partly based on the ++ * priorities of the monitored sinks. */ ++ pa_core_update_default_source(core); ++ ++ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); ++ pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], core->default_sink); ++} ++ ++/* a < b -> return -1 ++ * a == b -> return 0 ++ * a > b -> return 1 */ ++static int compare_sources(pa_source *a, pa_source *b) { ++ pa_core *core; ++ ++ core = a->core; ++ ++ /* The configured default source is preferred over any other source. */ ++ if (b == core->configured_default_source) ++ return -1; ++ if (a == core->configured_default_source) ++ return 1; ++ ++ /* Monitor sources lose to non-monitor sources. */ ++ if (a->monitor_of && !b->monitor_of) ++ return -1; ++ if (!a->monitor_of && b->monitor_of) ++ return 1; ++ ++ if (a->priority < b->priority) ++ return -1; ++ if (a->priority > b->priority) ++ return 1; ++ ++ /* If the sources are monitors, we can compare the monitored sinks. */ ++ if (a->monitor_of) ++ return compare_sinks(a->monitor_of, b->monitor_of); ++ ++ /* It's hard to find any difference between these sources, but maybe one of ++ * them is already the default source? If so, it's best to keep it as the ++ * default to avoid changing the routing for no good reason. */ ++ if (b == core->default_source) ++ return -1; ++ if (a == core->default_source) ++ return 1; ++ ++ return 0; ++} ++ ++void pa_core_update_default_source(pa_core *core) { ++ pa_source *best = NULL; ++ pa_source *source; ++ uint32_t idx; ++ pa_source *old_default_source; ++ ++ pa_assert(core); ++ ++ PA_IDXSET_FOREACH(source, core->sources, idx) { ++ if (!best) { ++ best = source; ++ continue; ++ } ++ ++ if (compare_sources(source, best) > 0) ++ best = source; ++ } ++ ++ old_default_source = core->default_source; ++ ++ if (best == old_default_source) ++ return; ++ ++ core->default_source = best; ++ pa_log_info("default_source: %s -> %s", ++ old_default_source ? old_default_source->name : "(unset)", best ? best->name : "(unset)"); ++ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); ++ pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], core->default_source); ++} ++ + static void exit_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_core *c = userdata; + pa_assert(c->exit_event == e); +diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h +index 802111ba1..6e613edeb 100644 +--- a/src/pulsecore/core.h ++++ b/src/pulsecore/core.h +@@ -160,9 +160,18 @@ struct pa_core { + /* Some hashmaps for all sorts of entities */ + pa_hashmap *namereg, *shared; + +- /* The default sink/source */ +- pa_source *default_source; ++ /* The default sink/source as configured by the user. If the user hasn't ++ * explicitly configured anything, these are set to NULL. */ ++ pa_sink *configured_default_sink; ++ pa_source *configured_default_source; ++ ++ /* The effective default sink/source. If no sink or source is explicitly ++ * configured as the default, we pick the device that ranks highest ++ * according to the compare_sinks() and compare_sources() functions in ++ * core.c. pa_core_update_default_sink/source() has to be called whenever ++ * anything changes that might change the comparison results. */ + pa_sink *default_sink; ++ pa_source *default_source; + + pa_channel_map default_channel_map; + pa_sample_spec default_sample_spec; +@@ -223,6 +232,21 @@ enum { + + pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size); + ++void pa_core_set_configured_default_sink(pa_core *core, pa_sink *sink); ++void pa_core_set_configured_default_source(pa_core *core, pa_source *source); ++ ++/* These should be called whenever something changes that may affect the ++ * default sink or source choice. ++ * ++ * If the default source choice happens between two monitor sources, the ++ * monitored sinks are compared, so if the default sink changes, the default ++ * source may change too. However, pa_core_update_default_sink() calls ++ * pa_core_update_default_source() internally, so it's sufficient to only call ++ * pa_core_update_default_sink() when something happens that affects the sink ++ * ordering. */ ++void pa_core_update_default_sink(pa_core *core); ++void pa_core_update_default_source(pa_core *core); ++ + /* Check whether no one is connected to this core */ + void pa_core_check_idle(pa_core *c); + +diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c +index 47bfc087f..0b7388592 100644 +--- a/src/pulsecore/namereg.c ++++ b/src/pulsecore/namereg.c +@@ -168,17 +168,6 @@ const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t + + pa_assert_se(pa_hashmap_put(c->namereg, e->name, e) >= 0); + +- /* If a sink or source is registered and there was none registered +- * before we inform the clients which then can ask for the default +- * sink/source which is then assigned. We don't adjust the default +- * sink/source here right away to give the module the chance to +- * register more sinks/sources before we choose a new default +- * sink/source. */ +- +- if ((!c->default_sink && type == PA_NAMEREG_SINK) || +- (!c->default_source && type == PA_NAMEREG_SOURCE)) +- pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); +- + return e->name; + } + +@@ -189,12 +178,6 @@ void pa_namereg_unregister(pa_core *c, const char *name) { + pa_assert(name); + + pa_assert_se(e = pa_hashmap_remove(c->namereg, name)); +- +- if (c->default_sink == e->data) +- pa_namereg_set_default_sink(c, NULL); +- else if (c->default_source == e->data) +- pa_namereg_set_default_source(c, NULL); +- + pa_xfree(e->name); + pa_xfree(e); + } +@@ -205,22 +188,16 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type) { + pa_assert(c); + + if (type == PA_NAMEREG_SOURCE && (!name || pa_streq(name, "@DEFAULT_SOURCE@"))) { +- pa_source *s; +- +- if ((s = pa_namereg_get_default_source(c))) +- return s; ++ return c->default_source; + + } else if (type == PA_NAMEREG_SINK && (!name || pa_streq(name, "@DEFAULT_SINK@"))) { +- pa_sink *s; +- +- if ((s = pa_namereg_get_default_sink(c))) +- return s; ++ return c->default_sink; + + } else if (type == PA_NAMEREG_SOURCE && name && pa_streq(name, "@DEFAULT_MONITOR@")) { +- pa_sink *s; +- +- if ((s = pa_namereg_get(c, NULL, PA_NAMEREG_SINK))) +- return s->monitor_source; ++ if (c->default_sink) ++ return c->default_sink->monitor_source; ++ else ++ return NULL; + } + + if (!name) +@@ -248,83 +225,3 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type) { + + return NULL; + } +- +-pa_sink* pa_namereg_set_default_sink(pa_core*c, pa_sink *s) { +- pa_assert(c); +- +- if (s && !PA_SINK_IS_LINKED(pa_sink_get_state(s))) +- return NULL; +- +- if (c->default_sink != s) { +- c->default_sink = s; +- pa_hook_fire(&c->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], c->default_sink); +- pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); +- } +- +- return s; +-} +- +-pa_source* pa_namereg_set_default_source(pa_core*c, pa_source *s) { +- pa_assert(c); +- +- if (s && !PA_SOURCE_IS_LINKED(pa_source_get_state(s))) +- return NULL; +- +- if (c->default_source != s) { +- c->default_source = s; +- pa_hook_fire(&c->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], c->default_source); +- pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); +- } +- +- return s; +-} +- +-pa_sink *pa_namereg_get_default_sink(pa_core *c) { +- pa_sink *s, *best = NULL; +- uint32_t idx; +- +- pa_assert(c); +- +- if (c->default_sink && PA_SINK_IS_LINKED(pa_sink_get_state(c->default_sink))) +- return c->default_sink; +- +- PA_IDXSET_FOREACH(s, c->sinks, idx) +- if (PA_SINK_IS_LINKED(pa_sink_get_state(s))) +- if (!best || s->priority > best->priority) +- best = s; +- +- return best; +-} +- +-pa_source *pa_namereg_get_default_source(pa_core *c) { +- pa_source *s, *best = NULL; +- uint32_t idx; +- +- pa_assert(c); +- +- if (c->default_source && PA_SOURCE_IS_LINKED(pa_source_get_state(c->default_source))) +- return c->default_source; +- +- /* First, try to find one that isn't a monitor */ +- PA_IDXSET_FOREACH(s, c->sources, idx) +- if (!s->monitor_of && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) +- if (!best || +- s->priority > best->priority) +- best = s; +- +- if (best) +- return best; +- +- /* Then, fallback to a monitor */ +- PA_IDXSET_FOREACH(s, c->sources, idx) +- if (PA_SOURCE_IS_LINKED(pa_source_get_state(s))) +- if (!best || +- s->priority > best->priority || +- (s->priority == best->priority && +- s->monitor_of && +- best->monitor_of && +- s->monitor_of->priority > best->monitor_of->priority)) +- best = s; +- +- return best; +-} +diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h +index 467db5d43..eb3475ad3 100644 +--- a/src/pulsecore/namereg.h ++++ b/src/pulsecore/namereg.h +@@ -36,12 +36,6 @@ const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t + void pa_namereg_unregister(pa_core *c, const char *name); + void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type); + +-pa_sink* pa_namereg_set_default_sink(pa_core*c, pa_sink *s); +-pa_source* pa_namereg_set_default_source(pa_core*c, pa_source *s); +- +-pa_sink *pa_namereg_get_default_sink(pa_core *c); +-pa_source *pa_namereg_get_default_source(pa_core *c); +- + bool pa_namereg_is_valid_name(const char *name); + bool pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type); + char* pa_namereg_make_valid_name(const char *name); +diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c +index 13f4f6238..8869ae4fa 100644 +--- a/src/pulsecore/protocol-native.c ++++ b/src/pulsecore/protocol-native.c +@@ -3637,10 +3637,9 @@ static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t t + static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_tagstruct *reply; +- pa_sink *def_sink; +- pa_source *def_source; + pa_sample_spec fixed_ss; + char *h, *u; ++ pa_core *core; + + pa_native_connection_assert_ref(c); + pa_assert(t); +@@ -3664,18 +3663,18 @@ static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t + pa_tagstruct_puts(reply, h); + pa_xfree(h); + +- fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec); ++ core = c->protocol->core; ++ ++ fixup_sample_spec(c, &fixed_ss, &core->default_sample_spec); + pa_tagstruct_put_sample_spec(reply, &fixed_ss); + +- def_sink = pa_namereg_get_default_sink(c->protocol->core); +- pa_tagstruct_puts(reply, def_sink ? def_sink->name : NULL); +- def_source = pa_namereg_get_default_source(c->protocol->core); +- pa_tagstruct_puts(reply, def_source ? def_source->name : NULL); ++ pa_tagstruct_puts(reply, core->default_sink ? core->default_sink->name : NULL); ++ pa_tagstruct_puts(reply, core->default_source ? core->default_source->name : NULL); + + pa_tagstruct_putu32(reply, c->protocol->core->cookie); + + if (c->version >= 15) +- pa_tagstruct_put_channel_map(reply, &c->protocol->core->default_channel_map); ++ pa_tagstruct_put_channel_map(reply, &core->default_channel_map); + + pa_pstream_send_tagstruct(c->pstream, reply); + } +@@ -4347,7 +4346,7 @@ static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t comman + source = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SOURCE); + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + +- pa_namereg_set_default_source(c->protocol->core, source); ++ pa_core_set_configured_default_source(c->protocol->core, source); + } else { + pa_sink *sink; + pa_assert(command == PA_COMMAND_SET_DEFAULT_SINK); +@@ -4355,7 +4354,7 @@ static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t comman + sink = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SINK); + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + +- pa_namereg_set_default_sink(c->protocol->core, sink); ++ pa_core_set_configured_default_sink(c->protocol->core, sink); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c +index ebb13c2c9..b4b373a58 100644 +--- a/src/pulsecore/sink.c ++++ b/src/pulsecore/sink.c +@@ -660,6 +660,8 @@ void pa_sink_put(pa_sink* s) { + + pa_source_put(s->monitor_source); + ++ pa_core_update_default_sink(s->core); ++ + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s); + } +@@ -690,6 +692,11 @@ void pa_sink_unlink(pa_sink* s) { + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sinks, s, NULL); + ++ if (s == s->core->configured_default_sink) ++ pa_core_set_configured_default_sink(s->core, NULL); ++ else ++ pa_core_update_default_sink(s->core); ++ + if (s->card) + pa_idxset_remove_by_data(s->card->sinks, s, NULL); + +diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c +index 3f0f53b52..ccf8efc91 100644 +--- a/src/pulsecore/source.c ++++ b/src/pulsecore/source.c +@@ -603,6 +603,8 @@ void pa_source_put(pa_source *s) { + else + pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0); + ++ pa_core_update_default_source(s->core); ++ + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PUT], s); + } +@@ -632,6 +634,11 @@ void pa_source_unlink(pa_source *s) { + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sources, s, NULL); + ++ if (s == s->core->configured_default_source) ++ pa_core_set_configured_default_source(s->core, NULL); ++ else ++ pa_core_update_default_source(s->core); ++ + if (s->card) + pa_idxset_remove_by_data(s->card->sources, s, NULL); + +-- +2.14.3 + diff --git a/SOURCES/0004-core-device-port-check-availability-when-choosing-th.patch b/SOURCES/0004-core-device-port-check-availability-when-choosing-th.patch new file mode 100644 index 0000000..9569aff --- /dev/null +++ b/SOURCES/0004-core-device-port-check-availability-when-choosing-th.patch @@ -0,0 +1,70 @@ +From fa883e254c28a36657b0f28443bd69ee5a1821e0 Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Thu, 16 Feb 2017 12:09:39 +0200 +Subject: [PATCH 4/4] core, device-port: check availability when choosing the + default device + +It doesn't make sense to use a sink or source whose active port is +unavailable, so let's take this into account when choosing the default +sink and source. +--- + src/pulsecore/core.c | 16 ++++++++++++++++ + src/pulsecore/device-port.c | 8 ++++++++ + 2 files changed, 24 insertions(+) + +diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c +index da1abb3ba..340b41ff3 100644 +--- a/src/pulsecore/core.c ++++ b/src/pulsecore/core.c +@@ -265,6 +265,14 @@ static int compare_sinks(pa_sink *a, pa_sink *b) { + + core = a->core; + ++ /* Available sinks always beat unavailable sinks. */ ++ if (a->active_port && a->active_port->available == PA_AVAILABLE_NO ++ && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) ++ return -1; ++ if (b->active_port && b->active_port->available == PA_AVAILABLE_NO ++ && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO)) ++ return 1; ++ + /* The configured default sink is preferred over any other sink. */ + if (b == core->configured_default_sink) + return -1; +@@ -331,6 +339,14 @@ static int compare_sources(pa_source *a, pa_source *b) { + + core = a->core; + ++ /* Available sources always beat unavailable sources. */ ++ if (a->active_port && a->active_port->available == PA_AVAILABLE_NO ++ && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) ++ return -1; ++ if (b->active_port && b->active_port->available == PA_AVAILABLE_NO ++ && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO)) ++ return 1; ++ + /* The configured default source is preferred over any other source. */ + if (b == core->configured_default_source) + return -1; +diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c +index 7c9ddf325..76a7e80a1 100644 +--- a/src/pulsecore/device-port.c ++++ b/src/pulsecore/device-port.c +@@ -93,6 +93,14 @@ void pa_device_port_set_available(pa_device_port *p, pa_available_t status) { + * be created before port objects, and then p->card could be non-NULL for + * the whole lifecycle of pa_device_port. */ + if (p->card) { ++ /* A sink or source whose active port is unavailable can't be the ++ * default sink/source, so port availability changes may affect the ++ * default sink/source choice. */ ++ if (p->direction == PA_DIRECTION_OUTPUT) ++ pa_core_update_default_sink(p->core); ++ else ++ pa_core_update_default_source(p->core); ++ + pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index); + pa_hook_fire(&p->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], p); + } +-- +2.14.3 + diff --git a/SOURCES/add-dell-dock-conf.path b/SOURCES/add-dell-dock-conf.path new file mode 100644 index 0000000..663fe18 --- /dev/null +++ b/SOURCES/add-dell-dock-conf.path @@ -0,0 +1,18 @@ +--- pulseaudio-10.0/src/Makefile.in 2017-01-18 04:54:18.000000000 +0100 ++++ pulseaudio-10.0-new/src/Makefile.in 2018-01-16 11:45:09.881076974 +0100 +@@ -3009,6 +3009,7 @@ + modules/alsa/mixer/paths/hdmi-output-7.conf + am__dist_alsaprofilesets_DATA_DIST = \ + modules/alsa/mixer/profile-sets/default.conf \ ++ modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf \ + modules/alsa/mixer/profile-sets/force-speaker.conf \ + modules/alsa/mixer/profile-sets/force-speaker-and-int-mic.conf \ + modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf \ +@@ -4244,6 +4245,7 @@ + libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la + @HAVE_ALSA_TRUE@dist_alsaprofilesets_DATA = \ + @HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/default.conf \ ++@HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf \ + @HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/force-speaker.conf \ + @HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/force-speaker-and-int-mic.conf \ + @HAVE_ALSA_TRUE@ modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf \ diff --git a/SPECS/pulseaudio.spec b/SPECS/pulseaudio.spec index fcb1a4c..e7484ba 100644 --- a/SPECS/pulseaudio.spec +++ b/SPECS/pulseaudio.spec @@ -25,7 +25,7 @@ Name: pulseaudio Summary: Improved Linux Sound Server Version: %{pa_major}%{?pa_minor:.%{pa_minor}} -Release: 3%{?snap:.%{snap}git%{shortcommit}}%{?dist} +Release: 5%{?snap:.%{snap}git%{shortcommit}}%{?dist} License: LGPLv2+ URL: http://www.freedesktop.org/wiki/Software/PulseAudio %if 0%{?gitrel} @@ -54,6 +54,11 @@ Patch2: pulseaudio-9.0-disable_flat_volumes.patch Patch3: pulseaudio-8.99.2-getaffinity.patch ## upstream patches +Patch101: 0001-alsa-mixer-Add-support-for-usb-audio-in-the-Dell-doc.patch +Patch102: 0002-sink-source-update-the-default-sink-source-on-port-s.patch +Patch103: 0003-improve-default-sink-source-handling.patch +Patch104: 0004-core-device-port-check-availability-when-choosing-th.patch +Patch105: add-dell-dock-conf.path ## upstreamable patches @@ -251,6 +256,11 @@ This package contains GDM integration hooks for the PulseAudio sound server. %patch1 -p1 -b .autostart %patch2 -p1 -b .disable_flat_volumes %patch3 -p1 -b .affinity +%patch101 -p1 -b .101 +%patch102 -p1 -b .102 +%patch103 -p1 -b .103 +%patch104 -p1 -b .104 +%patch105 -p1 -b .105 sed -i.no_consolekit -e \ 's/^load-module module-console-kit/#load-module module-console-kit/' \ @@ -591,6 +601,15 @@ exit 0 %attr(0600, gdm, gdm) %{_localstatedir}/lib/gdm/.pulse/default.pa %changelog +* Tue Jan 16 2018 Wim Taymans - 10.0-5 +- Dist the new config file for Dell dock TB16 +- Resolves: #1457385 + +* Wed Aug 09 2017 Wim Taymans - 10.0-4 +- Add support for usb audio in the Dell dock TB16 +- Add patches to improve default sinks +- Resolves: #1457385 + * Thu Mar 09 2017 Wim Taymans - 10.0-3 - Add more Requires to avoid testing multiple versions - Resolves: #1387036