mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-10-29 05:40:23 -04:00 
			
		
		
		
	modules: add module-intended-roles that automatically puts streams marked with a role on devices that are intended for that role
This commit is contained in:
		
							parent
							
								
									c4d90ea986
								
							
						
					
					
						commit
						27af46045b
					
				
					 2 changed files with 447 additions and 0 deletions
				
			
		|  | @ -974,6 +974,7 @@ modlibexec_LTLIBRARIES += \ | |||
| 		module-default-device-restore.la \ | ||||
| 		module-always-sink.la \ | ||||
| 		module-rescue-streams.la \ | ||||
| 		module-intended-roles.la \ | ||||
| 		module-suspend-on-idle.la \ | ||||
| 		module-http-protocol-tcp.la \ | ||||
| 		module-sine.la \ | ||||
|  | @ -1206,6 +1207,7 @@ SYMDEF_FILES = \ | |||
| 		modules/module-default-device-restore-symdef.h \ | ||||
| 		modules/module-always-sink-symdef.h \ | ||||
| 		modules/module-rescue-streams-symdef.h \ | ||||
| 		modules/module-intended-roles-symdef.h \ | ||||
| 		modules/module-suspend-on-idle-symdef.h \ | ||||
| 		modules/module-hal-detect-symdef.h \ | ||||
| 		modules/module-udev-detect-symdef.h \ | ||||
|  | @ -1538,6 +1540,12 @@ module_rescue_streams_la_LDFLAGS = $(MODULE_LDFLAGS) | |||
| module_rescue_streams_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la | ||||
| module_rescue_streams_la_CFLAGS = $(AM_CFLAGS) | ||||
| 
 | ||||
| # Automatically move streams to devices that are intended for their roles | ||||
| module_intended_roles_la_SOURCES = modules/module-intended-roles.c | ||||
| module_intended_roles_la_LDFLAGS = $(MODULE_LDFLAGS) | ||||
| module_intended_roles_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la | ||||
| module_intended_roles_la_CFLAGS = $(AM_CFLAGS) | ||||
| 
 | ||||
| # Suspend-on-idle module | ||||
| module_suspend_on_idle_la_SOURCES = modules/module-suspend-on-idle.c | ||||
| module_suspend_on_idle_la_LDFLAGS = $(MODULE_LDFLAGS) | ||||
|  |  | |||
							
								
								
									
										439
									
								
								src/modules/module-intended-roles.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										439
									
								
								src/modules/module-intended-roles.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,439 @@ | |||
| /***
 | ||||
|   This file is part of PulseAudio. | ||||
| 
 | ||||
|   Copyright 2009 Lennart Poettering | ||||
| 
 | ||||
|   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, write to the Free Software | ||||
|   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||
|   USA. | ||||
| ***/ | ||||
| 
 | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include <config.h> | ||||
| #endif | ||||
| 
 | ||||
| #include <unistd.h> | ||||
| #include <string.h> | ||||
| #include <errno.h> | ||||
| #include <sys/types.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <ctype.h> | ||||
| 
 | ||||
| #include <pulse/xmalloc.h> | ||||
| #include <pulse/volume.h> | ||||
| #include <pulse/timeval.h> | ||||
| #include <pulse/util.h> | ||||
| 
 | ||||
| #include <pulsecore/core-error.h> | ||||
| #include <pulsecore/module.h> | ||||
| #include <pulsecore/core-util.h> | ||||
| #include <pulsecore/modargs.h> | ||||
| #include <pulsecore/log.h> | ||||
| #include <pulsecore/core-subscribe.h> | ||||
| #include <pulsecore/sink-input.h> | ||||
| #include <pulsecore/source-output.h> | ||||
| #include <pulsecore/namereg.h> | ||||
| #include <pulsecore/protocol-native.h> | ||||
| #include <pulsecore/pstream.h> | ||||
| #include <pulsecore/pstream-util.h> | ||||
| #include <pulsecore/database.h> | ||||
| 
 | ||||
| #include "module-stream-restore-symdef.h" | ||||
| 
 | ||||
| PA_MODULE_AUTHOR("Lennart Poettering"); | ||||
| PA_MODULE_DESCRIPTION("Automatically set device of streams based of intended roles of devices"); | ||||
| PA_MODULE_VERSION(PACKAGE_VERSION); | ||||
| PA_MODULE_LOAD_ONCE(TRUE); | ||||
| PA_MODULE_USAGE( | ||||
|         "on_hotplug=<When new device becomes available, recheck streams?> " | ||||
|         "on_rescue=<When device becomes unavailable, recheck streams?>"); | ||||
| 
 | ||||
| static const char* const valid_modargs[] = { | ||||
|     "on_hotplug", | ||||
|     "on_rescue", | ||||
|     NULL | ||||
| }; | ||||
| 
 | ||||
| struct userdata { | ||||
|     pa_core *core; | ||||
|     pa_module *module; | ||||
|     pa_hook_slot | ||||
|         *sink_input_new_hook_slot, | ||||
|         *source_output_new_hook_slot, | ||||
|         *sink_put_hook_slot, | ||||
|         *source_put_hook_slot, | ||||
|         *sink_unlink_hook_slot, | ||||
|         *source_unlink_hook_slot; | ||||
| 
 | ||||
|     pa_bool_t on_hotplug:1; | ||||
|     pa_bool_t on_rescue:1; | ||||
| }; | ||||
| 
 | ||||
| static pa_bool_t role_match(pa_proplist *proplist, const char *role) { | ||||
|     const char *ir; | ||||
|     char *r; | ||||
|     const char *state; | ||||
| 
 | ||||
|     if (!(ir = pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES))) | ||||
|         return FALSE; | ||||
| 
 | ||||
|     while ((r = pa_split_spaces(ir, &state))) { | ||||
| 
 | ||||
|         if (pa_streq(role, r)) { | ||||
|             pa_xfree(r); | ||||
|             return TRUE; | ||||
|         } | ||||
| 
 | ||||
|         pa_xfree(r); | ||||
|     } | ||||
| 
 | ||||
|     return FALSE; | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|     uint32_t idx; | ||||
| 
 | ||||
|     pa_assert(c); | ||||
|     pa_assert(new_data); | ||||
|     pa_assert(u); | ||||
| 
 | ||||
|     if (!new_data->proplist) { | ||||
|         pa_log_debug("New stream lacks property data."); | ||||
|         return PA_HOOK_OK; | ||||
|     } | ||||
| 
 | ||||
|     if (new_data->sink) { | ||||
|         pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); | ||||
|         return PA_HOOK_OK; | ||||
|     } | ||||
| 
 | ||||
|     if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { | ||||
|         pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); | ||||
|         return PA_HOOK_OK; | ||||
|     } | ||||
| 
 | ||||
|     /* 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)) { | ||||
|             new_data->sink = def; | ||||
|             new_data->save_sink = FALSE; | ||||
|             return PA_HOOK_OK; | ||||
|         } | ||||
| 
 | ||||
|     PA_IDXSET_FOREACH(s, c->sinks, idx) { | ||||
|         if (s == def) | ||||
|             continue; | ||||
| 
 | ||||
|         if (role_match(s->proplist, role)) { | ||||
|             new_data->sink = s; | ||||
|             new_data->save_sink = FALSE; | ||||
|             return PA_HOOK_OK; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|     uint32_t idx; | ||||
| 
 | ||||
|     pa_assert(c); | ||||
|     pa_assert(new_data); | ||||
|     pa_assert(u); | ||||
| 
 | ||||
|     if (!new_data->proplist) { | ||||
|         pa_log_debug("New stream lacks property data."); | ||||
|         return PA_HOOK_OK; | ||||
|     } | ||||
| 
 | ||||
|     if (new_data->source) { | ||||
|         pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); | ||||
|         return PA_HOOK_OK; | ||||
|     } | ||||
| 
 | ||||
|     if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { | ||||
|         pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); | ||||
|         return PA_HOOK_OK; | ||||
|     } | ||||
| 
 | ||||
|     /* 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)) { | ||||
|             new_data->source = def; | ||||
|             new_data->save_source = FALSE; | ||||
|             return PA_HOOK_OK; | ||||
|         } | ||||
| 
 | ||||
|     PA_IDXSET_FOREACH(s, c->sources, idx) { | ||||
|         if (s == def) | ||||
|             continue; | ||||
| 
 | ||||
|         if (role_match(s->proplist, role)) { | ||||
|             new_data->source = s; | ||||
|             new_data->save_source = FALSE; | ||||
|             return PA_HOOK_OK; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
| static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { | ||||
|     pa_sink_input *si; | ||||
|     uint32_t idx; | ||||
| 
 | ||||
|     pa_assert(c); | ||||
|     pa_assert(sink); | ||||
|     pa_assert(u); | ||||
|     pa_assert(u->on_hotplug); | ||||
| 
 | ||||
|     PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { | ||||
|         const char *role; | ||||
| 
 | ||||
|         if (si->sink == sink) | ||||
|             continue; | ||||
| 
 | ||||
|         if (si->save_sink) | ||||
|             continue; | ||||
| 
 | ||||
|         if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) | ||||
|             continue; | ||||
| 
 | ||||
|         if (role_match(si->sink->proplist, role)) | ||||
|             continue; | ||||
| 
 | ||||
|         if (!role_match(sink->proplist, role)) | ||||
|             continue; | ||||
| 
 | ||||
|         pa_sink_input_move_to(si, sink, FALSE); | ||||
|     } | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
| static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { | ||||
|     pa_source_output *so; | ||||
|     uint32_t idx; | ||||
| 
 | ||||
|     pa_assert(c); | ||||
|     pa_assert(source); | ||||
|     pa_assert(u); | ||||
|     pa_assert(u->on_hotplug); | ||||
| 
 | ||||
|     PA_IDXSET_FOREACH(so, c->source_outputs, idx) { | ||||
|         const char *role; | ||||
| 
 | ||||
|         if (so->source == source) | ||||
|             continue; | ||||
| 
 | ||||
|         if (so->save_source) | ||||
|             continue; | ||||
| 
 | ||||
|         if (so->direct_on_input) | ||||
|             continue; | ||||
| 
 | ||||
|         if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) | ||||
|             continue; | ||||
| 
 | ||||
|         if (role_match(so->source->proplist, role)) | ||||
|             continue; | ||||
| 
 | ||||
|         if (!role_match(source->proplist, role)) | ||||
|             continue; | ||||
| 
 | ||||
|         pa_source_output_move_to(so, source, FALSE); | ||||
|     } | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|     pa_assert(u); | ||||
|     pa_assert(u->on_rescue); | ||||
| 
 | ||||
|     /* There's no point in doing anything if the core is shut down anyway */ | ||||
|     if (c->state == PA_CORE_SHUTDOWN) | ||||
|         return PA_HOOK_OK; | ||||
| 
 | ||||
|     /* If there not default sink, then there is no sink at all */ | ||||
|     if (!(def = pa_namereg_get_default_sink(c))) | ||||
|         return PA_HOOK_OK; | ||||
| 
 | ||||
|     PA_IDXSET_FOREACH(si, sink->inputs, idx) { | ||||
|         const char *role; | ||||
|         uint32_t jdx; | ||||
|         pa_sink *d; | ||||
| 
 | ||||
|         if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) | ||||
|             continue; | ||||
| 
 | ||||
|         /* Would the default sink fit? If so, let's use it */ | ||||
|         if (def != sink && role_match(def->proplist, role)) { | ||||
|             pa_sink_input_move_to(si, def, FALSE); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         /* Try to find some other fitting sink */ | ||||
|         PA_IDXSET_FOREACH(d, c->sinks, jdx) { | ||||
|             if (d == def || d == sink) | ||||
|                 continue; | ||||
| 
 | ||||
|             if (role_match(d->proplist, role)) { | ||||
|                 pa_sink_input_move_to(si, d, FALSE); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|     pa_assert(u); | ||||
|     pa_assert(u->on_rescue); | ||||
| 
 | ||||
|     /* There's no point in doing anything if the core is shut down anyway */ | ||||
|     if (c->state == PA_CORE_SHUTDOWN) | ||||
|         return PA_HOOK_OK; | ||||
| 
 | ||||
|     /* If there not default source, then there is no source at all */ | ||||
|     if (!(def = pa_namereg_get_default_source(c))) | ||||
|         return PA_HOOK_OK; | ||||
| 
 | ||||
|     PA_IDXSET_FOREACH(so, source->outputs, idx) { | ||||
|         const char *role; | ||||
|         uint32_t jdx; | ||||
|         pa_source *d; | ||||
| 
 | ||||
|         if (so->direct_on_input) | ||||
|             continue; | ||||
| 
 | ||||
|         if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) | ||||
|             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); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         /* Try to find some other fitting source */ | ||||
|         PA_IDXSET_FOREACH(d, c->sources, jdx) { | ||||
|             if (d == def || d == source) | ||||
|                 continue; | ||||
| 
 | ||||
|             if (role_match(d->proplist, role) && !source->monitor_of == !d->monitor_of) { | ||||
|                 pa_source_output_move_to(so, d, FALSE); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
| int pa__init(pa_module*m) { | ||||
|     pa_modargs *ma = NULL; | ||||
|     struct userdata *u; | ||||
|     pa_bool_t on_hotplug = TRUE, on_rescue = TRUE; | ||||
| 
 | ||||
|     pa_assert(m); | ||||
| 
 | ||||
|     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { | ||||
|         pa_log("Failed to parse module arguments"); | ||||
|         goto fail; | ||||
|     } | ||||
| 
 | ||||
|     if (pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || | ||||
|         pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { | ||||
|         pa_log("on_hotplug= and on_rescue= expect boolean arguments"); | ||||
|         goto fail; | ||||
|     } | ||||
| 
 | ||||
|     m->userdata = u = pa_xnew0(struct userdata, 1); | ||||
|     u->core = m->core; | ||||
|     u->module = m; | ||||
|     u->on_hotplug = on_hotplug; | ||||
|     u->on_rescue = on_rescue; | ||||
| 
 | ||||
|     /* A little bit later than module-stream-restore */ | ||||
|     u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) sink_input_new_hook_callback, u); | ||||
|     u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) source_output_new_hook_callback, u); | ||||
| 
 | ||||
|     if (on_hotplug) { | ||||
|         /* A little bit later than module-stream-restore */ | ||||
|         u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, u); | ||||
|         u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) source_put_hook_callback, u); | ||||
|     } | ||||
| 
 | ||||
|     if (on_rescue) { | ||||
|         /* A little bit later than module-stream-restore, a little bit earlier than module-rescue-streams, ... */ | ||||
|         u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_unlink_hook_callback, NULL); | ||||
|         u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+10, (pa_hook_cb_t) source_unlink_hook_callback, NULL); | ||||
|     } | ||||
| 
 | ||||
|     pa_modargs_free(ma); | ||||
|     return 0; | ||||
| 
 | ||||
| fail: | ||||
|     pa__done(m); | ||||
| 
 | ||||
|     if (ma) | ||||
|         pa_modargs_free(ma); | ||||
| 
 | ||||
|     return  -1; | ||||
| } | ||||
| 
 | ||||
| void pa__done(pa_module*m) { | ||||
|     struct userdata* u; | ||||
| 
 | ||||
|     pa_assert(m); | ||||
| 
 | ||||
|     if (!(u = m->userdata)) | ||||
|         return; | ||||
| 
 | ||||
|     if (u->sink_input_new_hook_slot) | ||||
|         pa_hook_slot_free(u->sink_input_new_hook_slot); | ||||
|     if (u->source_output_new_hook_slot) | ||||
|         pa_hook_slot_free(u->source_output_new_hook_slot); | ||||
| 
 | ||||
|     if (u->sink_put_hook_slot) | ||||
|         pa_hook_slot_free(u->sink_put_hook_slot); | ||||
|     if (u->source_put_hook_slot) | ||||
|         pa_hook_slot_free(u->source_put_hook_slot); | ||||
| 
 | ||||
|     if (u->sink_unlink_hook_slot) | ||||
|         pa_hook_slot_free(u->sink_unlink_hook_slot); | ||||
|     if (u->source_unlink_hook_slot) | ||||
|         pa_hook_slot_free(u->source_unlink_hook_slot); | ||||
| 
 | ||||
|     pa_xfree(u); | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lennart Poettering
						Lennart Poettering