1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2008 Colin Guthrie 5 Copyright 2017 Sebastian Dröge <sebastian@centricular.com> 6 7 PulseAudio is free software; you can redistribute it and/or modify 8 it under the terms of the GNU Lesser General Public License as published 9 by the Free Software Foundation; either version 2.1 of the License, 10 or (at your option) any later version. 11 12 PulseAudio is distributed in the hope that it will be useful, but 13 WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 General Public License for more details. 16 17 You should have received a copy of the GNU Lesser General Public License 18 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 19***/ 20 21#ifdef HAVE_CONFIG_H 22#include <config.h> 23#endif 24 25#include <pulse/xmalloc.h> 26 27#include <pulsecore/core.h> 28#include <pulsecore/core-util.h> 29#include <pulsecore/i18n.h> 30#include <pulsecore/source.h> 31#include <pulsecore/modargs.h> 32#include <pulsecore/log.h> 33 34PA_MODULE_AUTHOR("Sebastian Dröge"); 35PA_MODULE_DESCRIPTION(_("Always keeps at least one source loaded even if it's a null one")); 36PA_MODULE_VERSION(PACKAGE_VERSION); 37PA_MODULE_LOAD_ONCE(true); 38PA_MODULE_USAGE( 39 "source_name=<name of source>"); 40 41#define DEFAULT_SOURCE_NAME "auto_null" 42 43static const char* const valid_modargs[] = { 44 "source_name", 45 NULL, 46}; 47 48struct userdata { 49 uint32_t null_module; 50 bool ignore; 51 char *source_name; 52}; 53 54static void load_null_source_if_needed(pa_core *c, pa_source *source, struct userdata* u) { 55 pa_source *target; 56 uint32_t idx; 57 char *t; 58 pa_module *m; 59 60 pa_assert(c); 61 pa_assert(u); 62 63 if (u->null_module != PA_INVALID_INDEX) 64 return; /* We've already got a null-source loaded */ 65 66 /* Loop through all sources and check to see if we have *any* 67 * sources. Ignore the source passed in (if it's not null), and 68 * don't count filter or monitor sources. */ 69 PA_IDXSET_FOREACH(target, c->sources, idx) 70 if (!source || ((target != source) && !pa_source_is_filter(target) && target->monitor_of == NULL)) 71 break; 72 73 if (target) 74 return; 75 76 pa_log_debug("Autoloading null-source as no other sources detected."); 77 78 u->ignore = true; 79 80 t = pa_sprintf_malloc("source_name=%s", u->source_name); 81 pa_module_load(&m, c, "module-null-source", t); 82 u->null_module = m ? m->index : PA_INVALID_INDEX; 83 pa_xfree(t); 84 85 u->ignore = false; 86 87 if (!m) 88 pa_log_warn("Unable to load module-null-source"); 89} 90 91static pa_hook_result_t put_hook_callback(pa_core *c, pa_source *source, void* userdata) { 92 struct userdata *u = userdata; 93 94 pa_assert(c); 95 pa_assert(source); 96 pa_assert(u); 97 98 /* This is us detecting ourselves on load... just ignore this. */ 99 if (u->ignore) 100 return PA_HOOK_OK; 101 102 /* There's no point in doing anything if the core is shut down anyway */ 103 if (c->state == PA_CORE_SHUTDOWN) 104 return PA_HOOK_OK; 105 106 /* Auto-loaded null-source not active, so ignoring newly detected source. */ 107 if (u->null_module == PA_INVALID_INDEX) 108 return PA_HOOK_OK; 109 110 /* This is us detecting ourselves on load in a different way... just ignore this too. */ 111 if (source->module && source->module->index == u->null_module) 112 return PA_HOOK_OK; 113 114 /* We don't count filter or monitor sources since they need a real source */ 115 if (pa_source_is_filter(source) || source->monitor_of != NULL) 116 return PA_HOOK_OK; 117 118 pa_log_info("A new source has been discovered. Unloading null-source."); 119 120 pa_module_unload_request_by_index(c, u->null_module, true); 121 u->null_module = PA_INVALID_INDEX; 122 123 return PA_HOOK_OK; 124} 125 126static pa_hook_result_t unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) { 127 struct userdata *u = userdata; 128 129 pa_assert(c); 130 pa_assert(source); 131 pa_assert(u); 132 133 /* First check to see if it's our own null-source that's been removed... */ 134 if (u->null_module != PA_INVALID_INDEX && source->module && source->module->index == u->null_module) { 135 pa_log_debug("Autoloaded null-source removed"); 136 u->null_module = PA_INVALID_INDEX; 137 return PA_HOOK_OK; 138 } 139 140 /* There's no point in doing anything if the core is shut down anyway */ 141 if (c->state == PA_CORE_SHUTDOWN) 142 return PA_HOOK_OK; 143 144 load_null_source_if_needed(c, source, u); 145 146 return PA_HOOK_OK; 147} 148 149int pa__init(pa_module*m) { 150 pa_modargs *ma = NULL; 151 struct userdata *u; 152 153 pa_assert(m); 154 155 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 156 pa_log("Failed to parse module arguments"); 157 return -1; 158 } 159 160 m->userdata = u = pa_xnew(struct userdata, 1); 161 u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); 162 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) put_hook_callback, u); 163 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) unlink_hook_callback, u); 164 u->null_module = PA_INVALID_INDEX; 165 u->ignore = false; 166 167 pa_modargs_free(ma); 168 169 load_null_source_if_needed(m->core, NULL, u); 170 171 return 0; 172} 173 174void pa__done(pa_module*m) { 175 struct userdata *u; 176 177 pa_assert(m); 178 179 if (!(u = m->userdata)) 180 return; 181 182 if (u->null_module != PA_INVALID_INDEX && m->core->state != PA_CORE_SHUTDOWN) 183 pa_module_unload_request_by_index(m->core, u->null_module, true); 184 185 pa_xfree(u->source_name); 186 pa_xfree(u); 187} 188