mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2026-01-09 13:20:25 +01:00
Currently module-switch-on-connect overwrites the default sink or source that the user has configured. This means that when the overwritten default sink or source becomes unavailable, the new default will be chosen based on priority and the default will not return to the originally configured value. This patch solves the issue by introducing new core variables for the sink or source chosen by the policy module which have higher priority than the user configured defaults. Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/784>
787 lines
24 KiB
C
787 lines
24 KiB
C
/***
|
|
This file is part of PulseAudio.
|
|
|
|
Copyright 2004-2006 Lennart Poettering
|
|
Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
|
|
#include <pulse/rtclock.h>
|
|
#include <pulse/timeval.h>
|
|
#include <pulse/xmalloc.h>
|
|
|
|
#include <pulsecore/module.h>
|
|
#include <pulsecore/core-rtclock.h>
|
|
#include <pulsecore/core-util.h>
|
|
#include <pulsecore/message-handler.h>
|
|
#include <pulsecore/core-scache.h>
|
|
#include <pulsecore/core-subscribe.h>
|
|
#include <pulsecore/random.h>
|
|
#include <pulsecore/log.h>
|
|
#include <pulsecore/macro.h>
|
|
#include <pulsecore/strbuf.h>
|
|
#include <pulsecore/namereg.h>
|
|
|
|
#include "core.h"
|
|
|
|
PA_DEFINE_PUBLIC_CLASS(pa_core, pa_msgobject);
|
|
|
|
static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
|
|
pa_core *c = PA_CORE(o);
|
|
|
|
pa_core_assert_ref(c);
|
|
|
|
switch (code) {
|
|
|
|
case PA_CORE_MESSAGE_UNLOAD_MODULE:
|
|
pa_module_unload(userdata, true);
|
|
return 0;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void core_free(pa_object *o);
|
|
|
|
/* Returns a list of handlers. */
|
|
static char *message_handler_list(pa_core *c) {
|
|
pa_json_encoder *encoder;
|
|
void *state = NULL;
|
|
struct pa_message_handler *handler;
|
|
|
|
encoder = pa_json_encoder_new();
|
|
|
|
pa_json_encoder_begin_element_array(encoder);
|
|
PA_HASHMAP_FOREACH(handler, c->message_handlers, state) {
|
|
pa_json_encoder_begin_element_object(encoder);
|
|
|
|
pa_json_encoder_add_member_string(encoder, "name", handler->object_path);
|
|
pa_json_encoder_add_member_string(encoder, "description", handler->description);
|
|
|
|
pa_json_encoder_end_object(encoder);
|
|
}
|
|
pa_json_encoder_end_array(encoder);
|
|
|
|
return pa_json_encoder_to_string_free(encoder);
|
|
}
|
|
|
|
static int core_message_handler(const char *object_path, const char *message, const pa_json_object *parameters, char **response, void *userdata) {
|
|
pa_core *c = userdata;
|
|
|
|
pa_assert(c);
|
|
pa_assert(message);
|
|
pa_assert(response);
|
|
pa_assert(pa_safe_streq(object_path, "/core"));
|
|
|
|
if (pa_streq(message, "list-handlers")) {
|
|
*response = message_handler_list(c);
|
|
return PA_OK;
|
|
}
|
|
|
|
return -PA_ERR_NOTIMPLEMENTED;
|
|
}
|
|
|
|
pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) {
|
|
pa_core* c;
|
|
pa_mempool *pool;
|
|
pa_mem_type_t type;
|
|
int j;
|
|
|
|
pa_assert(m);
|
|
|
|
if (shared) {
|
|
type = (enable_memfd) ? PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX;
|
|
if (!(pool = pa_mempool_new(type, shm_size, false))) {
|
|
pa_log_warn("Failed to allocate %s memory pool. Falling back to a normal memory pool.",
|
|
pa_mem_type_to_string(type));
|
|
shared = false;
|
|
}
|
|
}
|
|
|
|
if (!shared) {
|
|
if (!(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, shm_size, false))) {
|
|
pa_log("pa_mempool_new() failed.");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
c = pa_msgobject_new(pa_core);
|
|
c->parent.parent.free = core_free;
|
|
c->parent.process_msg = core_process_msg;
|
|
|
|
c->state = PA_CORE_STARTUP;
|
|
c->mainloop = m;
|
|
|
|
c->clients = pa_idxset_new(NULL, NULL);
|
|
c->cards = pa_idxset_new(NULL, NULL);
|
|
c->sinks = pa_idxset_new(NULL, NULL);
|
|
c->sources = pa_idxset_new(NULL, NULL);
|
|
c->sink_inputs = pa_idxset_new(NULL, NULL);
|
|
c->source_outputs = pa_idxset_new(NULL, NULL);
|
|
c->modules = pa_idxset_new(NULL, NULL);
|
|
c->scache = pa_idxset_new(NULL, NULL);
|
|
|
|
c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
|
c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
|
c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
|
|
|
pa_message_handler_register(c, "/core", "Core message handler", core_message_handler, (void *) c);
|
|
|
|
c->default_source = NULL;
|
|
c->default_sink = NULL;
|
|
c->configured_default_source = NULL;
|
|
c->configured_default_sink = NULL;
|
|
c->policy_default_source = NULL;
|
|
c->policy_default_sink = NULL;
|
|
|
|
c->default_sample_spec.format = PA_SAMPLE_S16NE;
|
|
c->default_sample_spec.rate = 44100;
|
|
c->default_sample_spec.channels = 2;
|
|
pa_channel_map_init_extend(&c->default_channel_map, c->default_sample_spec.channels, PA_CHANNEL_MAP_DEFAULT);
|
|
c->default_n_fragments = 4;
|
|
c->default_fragment_size_msec = 25;
|
|
|
|
c->deferred_volume_safety_margin_usec = 8000;
|
|
c->deferred_volume_extra_delay_usec = 0;
|
|
|
|
c->module_defer_unload_event = NULL;
|
|
c->modules_pending_unload = pa_hashmap_new(NULL, NULL);
|
|
|
|
c->subscription_defer_event = NULL;
|
|
PA_LLIST_HEAD_INIT(pa_subscription, c->subscriptions);
|
|
PA_LLIST_HEAD_INIT(pa_subscription_event, c->subscription_event_queue);
|
|
c->subscription_event_last = NULL;
|
|
|
|
c->mempool = pool;
|
|
c->shm_size = shm_size;
|
|
pa_silence_cache_init(&c->silence_cache);
|
|
|
|
c->exit_event = NULL;
|
|
c->scache_auto_unload_event = NULL;
|
|
|
|
c->exit_idle_time = -1;
|
|
c->scache_idle_time = 20;
|
|
|
|
c->flat_volumes = true;
|
|
c->disallow_module_loading = false;
|
|
c->disallow_exit = false;
|
|
c->running_as_daemon = false;
|
|
c->realtime_scheduling = false;
|
|
c->realtime_priority = 5;
|
|
c->disable_remixing = false;
|
|
c->remixing_use_all_sink_channels = true;
|
|
c->remixing_produce_lfe = false;
|
|
c->remixing_consume_lfe = false;
|
|
c->lfe_crossover_freq = 0;
|
|
c->deferred_volume = true;
|
|
c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
|
|
|
|
for (j = 0; j < PA_CORE_HOOK_MAX; j++)
|
|
pa_hook_init(&c->hooks[j], c);
|
|
|
|
pa_random(&c->cookie, sizeof(c->cookie));
|
|
|
|
#ifdef SIGPIPE
|
|
pa_check_signal_is_blocked(SIGPIPE);
|
|
#endif
|
|
|
|
return c;
|
|
}
|
|
|
|
static void core_free(pa_object *o) {
|
|
pa_core *c = PA_CORE(o);
|
|
int j;
|
|
pa_assert(c);
|
|
|
|
c->state = PA_CORE_SHUTDOWN;
|
|
|
|
/* Note: All modules and samples in the cache should be unloaded before
|
|
* we get here */
|
|
|
|
pa_assert(pa_idxset_isempty(c->scache));
|
|
pa_idxset_free(c->scache, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->modules));
|
|
pa_idxset_free(c->modules, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->clients));
|
|
pa_idxset_free(c->clients, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->cards));
|
|
pa_idxset_free(c->cards, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->sinks));
|
|
pa_idxset_free(c->sinks, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->sources));
|
|
pa_idxset_free(c->sources, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->source_outputs));
|
|
pa_idxset_free(c->source_outputs, NULL);
|
|
|
|
pa_assert(pa_idxset_isempty(c->sink_inputs));
|
|
pa_idxset_free(c->sink_inputs, NULL);
|
|
|
|
pa_assert(pa_hashmap_isempty(c->namereg));
|
|
pa_hashmap_free(c->namereg);
|
|
|
|
pa_assert(pa_hashmap_isempty(c->shared));
|
|
pa_hashmap_free(c->shared);
|
|
|
|
pa_message_handler_unregister(c, "/core");
|
|
|
|
pa_assert(pa_hashmap_isempty(c->message_handlers));
|
|
pa_hashmap_free(c->message_handlers);
|
|
|
|
pa_assert(pa_hashmap_isempty(c->modules_pending_unload));
|
|
pa_hashmap_free(c->modules_pending_unload);
|
|
|
|
pa_subscription_free_all(c);
|
|
|
|
if (c->exit_event)
|
|
c->mainloop->time_free(c->exit_event);
|
|
|
|
pa_assert(!c->default_source);
|
|
pa_assert(!c->default_sink);
|
|
pa_xfree(c->configured_default_source);
|
|
pa_xfree(c->configured_default_sink);
|
|
pa_xfree(c->policy_default_source);
|
|
pa_xfree(c->policy_default_sink);
|
|
|
|
pa_silence_cache_done(&c->silence_cache);
|
|
pa_mempool_unref(c->mempool);
|
|
|
|
for (j = 0; j < PA_CORE_HOOK_MAX; j++)
|
|
pa_hook_done(&c->hooks[j]);
|
|
|
|
pa_xfree(c);
|
|
}
|
|
|
|
static bool is_sink_available(pa_core *core, const char *sink_name) {
|
|
pa_sink *sink;
|
|
|
|
if (!(sink = pa_namereg_get(core, sink_name, PA_NAMEREG_SINK)))
|
|
return false;
|
|
|
|
if (!PA_SINK_IS_LINKED(sink->state))
|
|
return false;
|
|
|
|
if (sink->active_port && sink->active_port->available == PA_AVAILABLE_NO)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool is_source_available(pa_core *core, const char *source_name) {
|
|
pa_source *source;
|
|
|
|
if (!(source = pa_namereg_get(core, source_name, PA_NAMEREG_SOURCE)))
|
|
return false;
|
|
|
|
if (!PA_SOURCE_IS_LINKED(source->state))
|
|
return false;
|
|
|
|
if (source->active_port && source->active_port->available == PA_AVAILABLE_NO)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void pa_core_set_configured_default_sink(pa_core *core, const char *sink) {
|
|
char *old_sink;
|
|
|
|
pa_assert(core);
|
|
|
|
old_sink = pa_xstrdup(core->configured_default_sink);
|
|
|
|
/* The default sink was overwritten by the policy default sink, but the user is
|
|
* now setting a new default manually. Clear the policy default sink. */
|
|
if (core->policy_default_sink && is_sink_available(core, core->policy_default_sink)) {
|
|
pa_xfree(core->policy_default_sink);
|
|
core->policy_default_sink = NULL;
|
|
|
|
} else if (pa_safe_streq(sink, old_sink))
|
|
goto finish;
|
|
|
|
pa_xfree(core->configured_default_sink);
|
|
core->configured_default_sink = pa_xstrdup(sink);
|
|
if (!pa_safe_streq(sink, old_sink)) {
|
|
pa_log_info("configured_default_sink: %s -> %s",
|
|
old_sink ? old_sink : "(unset)", sink ? sink : "(unset)");
|
|
}
|
|
pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
|
|
|
|
pa_core_update_default_sink(core);
|
|
|
|
finish:
|
|
pa_xfree(old_sink);
|
|
}
|
|
|
|
void pa_core_set_configured_default_source(pa_core *core, const char *source) {
|
|
char *old_source;
|
|
|
|
pa_assert(core);
|
|
|
|
old_source = pa_xstrdup(core->configured_default_source);
|
|
|
|
/* The default source was overwritten by the policy default source, but the user is
|
|
* now setting a new default manually. Clear the policy default source. */
|
|
if (core->policy_default_source && is_source_available(core, core->policy_default_source)) {
|
|
pa_xfree(core->policy_default_source);
|
|
core->policy_default_source = NULL;
|
|
|
|
} else if (pa_safe_streq(source, old_source))
|
|
goto finish;
|
|
|
|
pa_xfree(core->configured_default_source);
|
|
core->configured_default_source = pa_xstrdup(source);
|
|
if (!pa_safe_streq(source, old_source)) {
|
|
pa_log_info("configured_default_source: %s -> %s",
|
|
old_source ? old_source : "(unset)", source ? source : "(unset)");
|
|
}
|
|
pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
|
|
|
|
pa_core_update_default_source(core);
|
|
|
|
finish:
|
|
pa_xfree(old_source);
|
|
}
|
|
|
|
void pa_core_set_policy_default_sink(pa_core *core, const char *sink) {
|
|
char *old_sink;
|
|
|
|
pa_assert(core);
|
|
|
|
old_sink = pa_xstrdup(core->policy_default_sink);
|
|
|
|
if (pa_safe_streq(sink, old_sink))
|
|
goto finish;
|
|
|
|
pa_xfree(core->policy_default_sink);
|
|
core->policy_default_sink = pa_xstrdup(sink);
|
|
pa_log_info("policy_default_sink: %s -> %s",
|
|
old_sink ? old_sink : "(unset)", sink ? sink : "(unset)");
|
|
pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
|
|
|
|
pa_core_update_default_sink(core);
|
|
|
|
finish:
|
|
pa_xfree(old_sink);
|
|
}
|
|
|
|
void pa_core_set_policy_default_source(pa_core *core, const char *source) {
|
|
char *old_source;
|
|
|
|
pa_assert(core);
|
|
|
|
old_source = pa_xstrdup(core->policy_default_source);
|
|
|
|
if (pa_safe_streq(source, old_source))
|
|
goto finish;
|
|
|
|
pa_xfree(core->policy_default_source);
|
|
core->policy_default_source = pa_xstrdup(source);
|
|
pa_log_info("policy_default_source: %s -> %s",
|
|
old_source ? old_source : "(unset)", source ? source : "(unset)");
|
|
pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
|
|
|
|
pa_core_update_default_source(core);
|
|
|
|
finish:
|
|
pa_xfree(old_source);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* 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 policy default sink is preferred over any other sink. */
|
|
if (pa_safe_streq(b->name, core->policy_default_sink))
|
|
return -1;
|
|
if (pa_safe_streq(a->name, core->policy_default_sink))
|
|
return 1;
|
|
|
|
/* The configured default sink is preferred over any other sink
|
|
* except the policy default sink. */
|
|
if (pa_safe_streq(b->name, core->configured_default_sink))
|
|
return -1;
|
|
if (pa_safe_streq(a->name, 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 (!PA_SINK_IS_LINKED(sink->state))
|
|
continue;
|
|
|
|
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);
|
|
|
|
/* try to move the streams from old_default_sink to the new default_sink conditionally */
|
|
if (old_default_sink)
|
|
pa_sink_move_streams_to_default_sink(core, old_default_sink, true);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* 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 policy default source is preferred over any other source. */
|
|
if (pa_safe_streq(b->name, core->policy_default_source))
|
|
return -1;
|
|
if (pa_safe_streq(a->name, core->policy_default_source))
|
|
return 1;
|
|
|
|
/* The configured default source is preferred over any other source
|
|
* except the policy default source. */
|
|
if (pa_safe_streq(b->name, core->configured_default_source))
|
|
return -1;
|
|
if (pa_safe_streq(a->name, 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 (!PA_SOURCE_IS_LINKED(source->state))
|
|
continue;
|
|
|
|
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);
|
|
|
|
/* try to move the streams from old_default_source to the new default_source conditionally */
|
|
if (old_default_source)
|
|
pa_source_move_streams_to_default_source(core, old_default_source, true);
|
|
}
|
|
|
|
void pa_core_set_exit_idle_time(pa_core *core, int time) {
|
|
pa_assert(core);
|
|
|
|
if (time == core->exit_idle_time)
|
|
return;
|
|
|
|
pa_log_info("exit_idle_time: %i -> %i", core->exit_idle_time, time);
|
|
core->exit_idle_time = time;
|
|
}
|
|
|
|
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);
|
|
|
|
pa_log_info("We are idle, quitting...");
|
|
pa_core_exit(c, true, 0);
|
|
}
|
|
|
|
void pa_core_check_idle(pa_core *c) {
|
|
pa_assert(c);
|
|
|
|
if (!c->exit_event &&
|
|
c->exit_idle_time >= 0 &&
|
|
pa_idxset_size(c->clients) == 0) {
|
|
|
|
c->exit_event = pa_core_rttime_new(c, pa_rtclock_now() + c->exit_idle_time * PA_USEC_PER_SEC, exit_callback, c);
|
|
|
|
} else if (c->exit_event && pa_idxset_size(c->clients) > 0) {
|
|
c->mainloop->time_free(c->exit_event);
|
|
c->exit_event = NULL;
|
|
}
|
|
}
|
|
|
|
int pa_core_exit(pa_core *c, bool force, int retval) {
|
|
pa_assert(c);
|
|
|
|
if (c->disallow_exit && !force)
|
|
return -1;
|
|
|
|
c->mainloop->quit(c->mainloop, retval);
|
|
return 0;
|
|
}
|
|
|
|
void pa_core_maybe_vacuum(pa_core *c) {
|
|
pa_assert(c);
|
|
|
|
if (pa_idxset_isempty(c->sink_inputs) && pa_idxset_isempty(c->source_outputs)) {
|
|
pa_log_debug("Hmm, no streams around, trying to vacuum.");
|
|
} else {
|
|
pa_sink *si;
|
|
pa_source *so;
|
|
uint32_t idx;
|
|
|
|
idx = 0;
|
|
PA_IDXSET_FOREACH(si, c->sinks, idx)
|
|
if (si->state != PA_SINK_SUSPENDED)
|
|
return;
|
|
|
|
idx = 0;
|
|
PA_IDXSET_FOREACH(so, c->sources, idx)
|
|
if (so->state != PA_SOURCE_SUSPENDED)
|
|
return;
|
|
|
|
pa_log_info("All sinks and sources are suspended, vacuuming memory");
|
|
}
|
|
|
|
pa_mempool_vacuum(c->mempool);
|
|
}
|
|
|
|
pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) {
|
|
struct timeval tv;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->mainloop);
|
|
|
|
return c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, usec, true), cb, userdata);
|
|
}
|
|
|
|
void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) {
|
|
struct timeval tv;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->mainloop);
|
|
|
|
c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, true));
|
|
}
|
|
|
|
void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s) {
|
|
pa_sink_input *si;
|
|
uint32_t idx;
|
|
|
|
pa_assert(c);
|
|
pa_assert(s);
|
|
|
|
PA_IDXSET_FOREACH(si, c->sink_inputs, idx) {
|
|
if (si->sink == s)
|
|
continue;
|
|
|
|
if (!si->sink)
|
|
continue;
|
|
|
|
/* Skip this sink input if it is connecting a filter sink to
|
|
* the master */
|
|
if (si->origin_sink)
|
|
continue;
|
|
|
|
/* It might happen that a stream and a sink are set up at the
|
|
same time, in which case we want to make sure we don't
|
|
interfere with that */
|
|
if (!PA_SINK_INPUT_IS_LINKED(si->state))
|
|
continue;
|
|
|
|
if (pa_safe_streq(si->preferred_sink, s->name))
|
|
pa_sink_input_move_to(si, s, false);
|
|
}
|
|
|
|
}
|
|
|
|
void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s) {
|
|
pa_source_output *so;
|
|
uint32_t idx;
|
|
|
|
pa_assert(c);
|
|
pa_assert(s);
|
|
|
|
PA_IDXSET_FOREACH(so, c->source_outputs, idx) {
|
|
if (so->source == s)
|
|
continue;
|
|
|
|
if (so->direct_on_input)
|
|
continue;
|
|
|
|
if (!so->source)
|
|
continue;
|
|
|
|
/* Skip this source output if it is connecting a filter source to
|
|
* the master */
|
|
if (so->destination_source)
|
|
continue;
|
|
|
|
/* It might happen that a stream and a source are set up at the
|
|
same time, in which case we want to make sure we don't
|
|
interfere with that */
|
|
if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state))
|
|
continue;
|
|
|
|
if (pa_safe_streq(so->preferred_source, s->name))
|
|
pa_source_output_move_to(so, s, false);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Helper macro to reduce repetition in pa_suspend_cause_to_string().
|
|
* Parameters:
|
|
* char *p: the current position in the write buffer
|
|
* bool first: is cause_to_check the first cause to be written?
|
|
* pa_suspend_cause_t cause_bitfield: the causes given to pa_suspend_cause_to_string()
|
|
* pa_suspend_cause_t cause_to_check: the cause whose presence in cause_bitfield is to be checked
|
|
*/
|
|
#define CHECK_CAUSE(p, first, cause_bitfield, cause_to_check) \
|
|
if (cause_bitfield & PA_SUSPEND_##cause_to_check) { \
|
|
size_t len = sizeof(#cause_to_check) - 1; \
|
|
if (!first) { \
|
|
*p = '|'; \
|
|
p++; \
|
|
} \
|
|
first = false; \
|
|
memcpy(p, #cause_to_check, len); \
|
|
p += len; \
|
|
}
|
|
|
|
const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause_bitfield, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]) {
|
|
char *p = buf;
|
|
bool first = true;
|
|
|
|
CHECK_CAUSE(p, first, cause_bitfield, USER);
|
|
CHECK_CAUSE(p, first, cause_bitfield, APPLICATION);
|
|
CHECK_CAUSE(p, first, cause_bitfield, IDLE);
|
|
CHECK_CAUSE(p, first, cause_bitfield, SESSION);
|
|
CHECK_CAUSE(p, first, cause_bitfield, PASSTHROUGH);
|
|
CHECK_CAUSE(p, first, cause_bitfield, INTERNAL);
|
|
CHECK_CAUSE(p, first, cause_bitfield, UNAVAILABLE);
|
|
|
|
if (p == buf) {
|
|
memcpy(p, "(none)", 6);
|
|
p += 6;
|
|
}
|
|
|
|
*p = 0;
|
|
|
|
return buf;
|
|
}
|