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
34 PA_MODULE_AUTHOR("Sebastian Dröge");
35 PA_MODULE_DESCRIPTION(_("Always keeps at least one source loaded even if it's a null one"));
36 PA_MODULE_VERSION(PACKAGE_VERSION);
37 PA_MODULE_LOAD_ONCE(true);
38 PA_MODULE_USAGE(
39 "source_name=<name of source>");
40
41 #define DEFAULT_SOURCE_NAME "auto_null"
42
43 static const char* const valid_modargs[] = {
44 "source_name",
45 NULL,
46 };
47
48 struct userdata {
49 uint32_t null_module;
50 bool ignore;
51 char *source_name;
52 };
53
load_null_source_if_needed(pa_core * c,pa_source * source,struct userdata * u)54 static 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
put_hook_callback(pa_core * c,pa_source * source,void * userdata)91 static 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
unlink_hook_callback(pa_core * c,pa_source * source,void * userdata)126 static 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
pa__init(pa_module * m)149 int 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
pa__done(pa_module * m)174 void 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