153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2004-2006 Lennart Poettering 553a5a1b3Sopenharmony_ci 653a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 753a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 853a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 953a5a1b3Sopenharmony_ci or (at your option) any later version. 1053a5a1b3Sopenharmony_ci 1153a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1253a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1353a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1453a5a1b3Sopenharmony_ci General Public License for more details. 1553a5a1b3Sopenharmony_ci 1653a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1753a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 1853a5a1b3Sopenharmony_ci***/ 1953a5a1b3Sopenharmony_ci 2053a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2153a5a1b3Sopenharmony_ci#include <config.h> 2253a5a1b3Sopenharmony_ci#endif 2353a5a1b3Sopenharmony_ci 2453a5a1b3Sopenharmony_ci#include <unistd.h> 2553a5a1b3Sopenharmony_ci#include <string.h> 2653a5a1b3Sopenharmony_ci#include <errno.h> 2753a5a1b3Sopenharmony_ci#include <sys/types.h> 2853a5a1b3Sopenharmony_ci#include <stdio.h> 2953a5a1b3Sopenharmony_ci#include <stdlib.h> 3053a5a1b3Sopenharmony_ci 3153a5a1b3Sopenharmony_ci#if defined(HAVE_REGEX_H) 3253a5a1b3Sopenharmony_ci#include <regex.h> 3353a5a1b3Sopenharmony_ci#elif defined(HAVE_PCREPOSIX_H) 3453a5a1b3Sopenharmony_ci#include <pcreposix.h> 3553a5a1b3Sopenharmony_ci#endif 3653a5a1b3Sopenharmony_ci 3753a5a1b3Sopenharmony_ci#include <pulse/xmalloc.h> 3853a5a1b3Sopenharmony_ci 3953a5a1b3Sopenharmony_ci#include <pulsecore/core-error.h> 4053a5a1b3Sopenharmony_ci#include <pulsecore/module.h> 4153a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 4253a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h> 4353a5a1b3Sopenharmony_ci#include <pulsecore/log.h> 4453a5a1b3Sopenharmony_ci#include <pulsecore/sink-input.h> 4553a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 4653a5a1b3Sopenharmony_ci 4753a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("Lennart Poettering"); 4853a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("Playback stream expression matching module"); 4953a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION); 5053a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(false); 5153a5a1b3Sopenharmony_ciPA_MODULE_USAGE("table=<filename> " 5253a5a1b3Sopenharmony_ci "key=<property_key>"); 5353a5a1b3Sopenharmony_ci 5453a5a1b3Sopenharmony_ci#define WHITESPACE "\n\r \t" 5553a5a1b3Sopenharmony_ci 5653a5a1b3Sopenharmony_ci#define DEFAULT_MATCH_TABLE_FILE PA_DEFAULT_CONFIG_DIR"/match.table" 5753a5a1b3Sopenharmony_ci#define DEFAULT_MATCH_TABLE_FILE_USER "match.table" 5853a5a1b3Sopenharmony_ci 5953a5a1b3Sopenharmony_ci#define UPDATE_REPLACE "replace" 6053a5a1b3Sopenharmony_ci#define UPDATE_MERGE "merge" 6153a5a1b3Sopenharmony_ci 6253a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = { 6353a5a1b3Sopenharmony_ci "table", 6453a5a1b3Sopenharmony_ci "key", 6553a5a1b3Sopenharmony_ci NULL, 6653a5a1b3Sopenharmony_ci}; 6753a5a1b3Sopenharmony_ci 6853a5a1b3Sopenharmony_cistruct rule { 6953a5a1b3Sopenharmony_ci regex_t regex; 7053a5a1b3Sopenharmony_ci pa_volume_t volume; 7153a5a1b3Sopenharmony_ci pa_proplist *proplist; 7253a5a1b3Sopenharmony_ci pa_update_mode_t mode; 7353a5a1b3Sopenharmony_ci struct rule *next; 7453a5a1b3Sopenharmony_ci}; 7553a5a1b3Sopenharmony_ci 7653a5a1b3Sopenharmony_cistruct userdata { 7753a5a1b3Sopenharmony_ci struct rule *rules; 7853a5a1b3Sopenharmony_ci char *property_key; 7953a5a1b3Sopenharmony_ci pa_hook_slot *sink_input_fixate_hook_slot; 8053a5a1b3Sopenharmony_ci}; 8153a5a1b3Sopenharmony_ci 8253a5a1b3Sopenharmony_cistatic int load_rules(struct userdata *u, const char *filename) { 8353a5a1b3Sopenharmony_ci FILE *f; 8453a5a1b3Sopenharmony_ci int n = 0; 8553a5a1b3Sopenharmony_ci int ret = -1; 8653a5a1b3Sopenharmony_ci struct rule *end = NULL; 8753a5a1b3Sopenharmony_ci char *fn = NULL; 8853a5a1b3Sopenharmony_ci 8953a5a1b3Sopenharmony_ci pa_assert(u); 9053a5a1b3Sopenharmony_ci 9153a5a1b3Sopenharmony_ci if (filename) 9253a5a1b3Sopenharmony_ci f = pa_fopen_cloexec(fn = pa_xstrdup(filename), "r"); 9353a5a1b3Sopenharmony_ci else 9453a5a1b3Sopenharmony_ci f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn); 9553a5a1b3Sopenharmony_ci 9653a5a1b3Sopenharmony_ci if (!f) { 9753a5a1b3Sopenharmony_ci pa_log("Failed to open file config file: %s", pa_cstrerror(errno)); 9853a5a1b3Sopenharmony_ci goto finish; 9953a5a1b3Sopenharmony_ci } 10053a5a1b3Sopenharmony_ci 10153a5a1b3Sopenharmony_ci pa_lock_fd(fileno(f), 1); 10253a5a1b3Sopenharmony_ci 10353a5a1b3Sopenharmony_ci while (!feof(f)) { 10453a5a1b3Sopenharmony_ci char *token_end, *value_str; 10553a5a1b3Sopenharmony_ci pa_volume_t volume = PA_VOLUME_NORM; 10653a5a1b3Sopenharmony_ci uint32_t k; 10753a5a1b3Sopenharmony_ci regex_t regex; 10853a5a1b3Sopenharmony_ci char ln[256]; 10953a5a1b3Sopenharmony_ci struct rule *rule; 11053a5a1b3Sopenharmony_ci pa_proplist *proplist = NULL; 11153a5a1b3Sopenharmony_ci pa_update_mode_t mode = (pa_update_mode_t) -1; 11253a5a1b3Sopenharmony_ci 11353a5a1b3Sopenharmony_ci if (!fgets(ln, sizeof(ln), f)) 11453a5a1b3Sopenharmony_ci break; 11553a5a1b3Sopenharmony_ci 11653a5a1b3Sopenharmony_ci n++; 11753a5a1b3Sopenharmony_ci 11853a5a1b3Sopenharmony_ci pa_strip_nl(ln); 11953a5a1b3Sopenharmony_ci 12053a5a1b3Sopenharmony_ci if (ln[0] == '#' || !*ln ) 12153a5a1b3Sopenharmony_ci continue; 12253a5a1b3Sopenharmony_ci 12353a5a1b3Sopenharmony_ci token_end = ln + strcspn(ln, WHITESPACE); 12453a5a1b3Sopenharmony_ci value_str = token_end + strspn(token_end, WHITESPACE); 12553a5a1b3Sopenharmony_ci *token_end = 0; 12653a5a1b3Sopenharmony_ci 12753a5a1b3Sopenharmony_ci if (!*ln) { 12853a5a1b3Sopenharmony_ci pa_log("[%s:%u] failed to parse line - missing regexp", fn, n); 12953a5a1b3Sopenharmony_ci goto finish; 13053a5a1b3Sopenharmony_ci } 13153a5a1b3Sopenharmony_ci 13253a5a1b3Sopenharmony_ci if (!*value_str) { 13353a5a1b3Sopenharmony_ci pa_log("[%s:%u] failed to parse line - too few words", fn, n); 13453a5a1b3Sopenharmony_ci goto finish; 13553a5a1b3Sopenharmony_ci } 13653a5a1b3Sopenharmony_ci 13753a5a1b3Sopenharmony_ci if (pa_atou(value_str, &k) >= 0) 13853a5a1b3Sopenharmony_ci volume = (pa_volume_t) PA_CLAMP_VOLUME(k); 13953a5a1b3Sopenharmony_ci else { 14053a5a1b3Sopenharmony_ci size_t len; 14153a5a1b3Sopenharmony_ci 14253a5a1b3Sopenharmony_ci token_end = value_str + strcspn(value_str, WHITESPACE); 14353a5a1b3Sopenharmony_ci 14453a5a1b3Sopenharmony_ci len = token_end - value_str; 14553a5a1b3Sopenharmony_ci if (len == (sizeof(UPDATE_REPLACE) - 1) && !strncmp(value_str, UPDATE_REPLACE, len)) 14653a5a1b3Sopenharmony_ci mode = PA_UPDATE_REPLACE; 14753a5a1b3Sopenharmony_ci else if (len == (sizeof(UPDATE_MERGE) - 1) && !strncmp(value_str, UPDATE_MERGE, len)) 14853a5a1b3Sopenharmony_ci mode = PA_UPDATE_MERGE; 14953a5a1b3Sopenharmony_ci 15053a5a1b3Sopenharmony_ci if (mode != (pa_update_mode_t) -1) { 15153a5a1b3Sopenharmony_ci value_str = token_end + strspn(token_end, WHITESPACE); 15253a5a1b3Sopenharmony_ci 15353a5a1b3Sopenharmony_ci if (!*value_str) { 15453a5a1b3Sopenharmony_ci pa_log("[%s:%u] failed to parse line - too few words", fn, n); 15553a5a1b3Sopenharmony_ci goto finish; 15653a5a1b3Sopenharmony_ci } 15753a5a1b3Sopenharmony_ci } else 15853a5a1b3Sopenharmony_ci mode = PA_UPDATE_MERGE; 15953a5a1b3Sopenharmony_ci 16053a5a1b3Sopenharmony_ci if (*value_str == '"') { 16153a5a1b3Sopenharmony_ci value_str++; 16253a5a1b3Sopenharmony_ci 16353a5a1b3Sopenharmony_ci token_end = strchr(value_str, '"'); 16453a5a1b3Sopenharmony_ci if (!token_end) { 16553a5a1b3Sopenharmony_ci pa_log("[%s:%u] failed to parse line - missing role closing quote", fn, n); 16653a5a1b3Sopenharmony_ci goto finish; 16753a5a1b3Sopenharmony_ci } 16853a5a1b3Sopenharmony_ci } else 16953a5a1b3Sopenharmony_ci token_end = value_str + strcspn(value_str, WHITESPACE); 17053a5a1b3Sopenharmony_ci 17153a5a1b3Sopenharmony_ci *token_end = 0; 17253a5a1b3Sopenharmony_ci 17353a5a1b3Sopenharmony_ci value_str = pa_sprintf_malloc("media.role=\"%s\"", value_str); 17453a5a1b3Sopenharmony_ci proplist = pa_proplist_from_string(value_str); 17553a5a1b3Sopenharmony_ci pa_xfree(value_str); 17653a5a1b3Sopenharmony_ci } 17753a5a1b3Sopenharmony_ci 17853a5a1b3Sopenharmony_ci if (regcomp(®ex, ln, REG_EXTENDED|REG_NOSUB) != 0) { 17953a5a1b3Sopenharmony_ci pa_log("[%s:%u] invalid regular expression", fn, n); 18053a5a1b3Sopenharmony_ci if (proplist) 18153a5a1b3Sopenharmony_ci pa_proplist_free(proplist); 18253a5a1b3Sopenharmony_ci goto finish; 18353a5a1b3Sopenharmony_ci } 18453a5a1b3Sopenharmony_ci 18553a5a1b3Sopenharmony_ci rule = pa_xnew(struct rule, 1); 18653a5a1b3Sopenharmony_ci rule->regex = regex; 18753a5a1b3Sopenharmony_ci rule->proplist = proplist; 18853a5a1b3Sopenharmony_ci rule->mode = mode; 18953a5a1b3Sopenharmony_ci rule->volume = volume; 19053a5a1b3Sopenharmony_ci rule->next = NULL; 19153a5a1b3Sopenharmony_ci 19253a5a1b3Sopenharmony_ci if (end) 19353a5a1b3Sopenharmony_ci end->next = rule; 19453a5a1b3Sopenharmony_ci else 19553a5a1b3Sopenharmony_ci u->rules = rule; 19653a5a1b3Sopenharmony_ci end = rule; 19753a5a1b3Sopenharmony_ci } 19853a5a1b3Sopenharmony_ci 19953a5a1b3Sopenharmony_ci ret = 0; 20053a5a1b3Sopenharmony_ci 20153a5a1b3Sopenharmony_cifinish: 20253a5a1b3Sopenharmony_ci if (f) { 20353a5a1b3Sopenharmony_ci pa_lock_fd(fileno(f), 0); 20453a5a1b3Sopenharmony_ci fclose(f); 20553a5a1b3Sopenharmony_ci } 20653a5a1b3Sopenharmony_ci 20753a5a1b3Sopenharmony_ci pa_xfree(fn); 20853a5a1b3Sopenharmony_ci 20953a5a1b3Sopenharmony_ci return ret; 21053a5a1b3Sopenharmony_ci} 21153a5a1b3Sopenharmony_ci 21253a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *si, struct userdata *u) { 21353a5a1b3Sopenharmony_ci struct rule *r; 21453a5a1b3Sopenharmony_ci const char *n; 21553a5a1b3Sopenharmony_ci 21653a5a1b3Sopenharmony_ci pa_assert(c); 21753a5a1b3Sopenharmony_ci pa_assert(u); 21853a5a1b3Sopenharmony_ci 21953a5a1b3Sopenharmony_ci if (!(n = pa_proplist_gets(si->proplist, u->property_key))) 22053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 22153a5a1b3Sopenharmony_ci 22253a5a1b3Sopenharmony_ci pa_log_debug("Matching with %s", n); 22353a5a1b3Sopenharmony_ci 22453a5a1b3Sopenharmony_ci for (r = u->rules; r; r = r->next) { 22553a5a1b3Sopenharmony_ci if (!regexec(&r->regex, n, 0, NULL, 0)) { 22653a5a1b3Sopenharmony_ci if (r->proplist) { 22753a5a1b3Sopenharmony_ci pa_log_debug("updating proplist of sink input '%s'", n); 22853a5a1b3Sopenharmony_ci pa_proplist_update(si->proplist, r->mode, r->proplist); 22953a5a1b3Sopenharmony_ci } else if (si->volume_writable) { 23053a5a1b3Sopenharmony_ci pa_cvolume cv; 23153a5a1b3Sopenharmony_ci pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume); 23253a5a1b3Sopenharmony_ci pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); 23353a5a1b3Sopenharmony_ci pa_sink_input_new_data_set_volume(si, &cv); 23453a5a1b3Sopenharmony_ci } else 23553a5a1b3Sopenharmony_ci pa_log_debug("the volume of sink input '%s' is not writable, can't change it", n); 23653a5a1b3Sopenharmony_ci } 23753a5a1b3Sopenharmony_ci } 23853a5a1b3Sopenharmony_ci 23953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 24053a5a1b3Sopenharmony_ci} 24153a5a1b3Sopenharmony_ci 24253a5a1b3Sopenharmony_ciint pa__init(pa_module*m) { 24353a5a1b3Sopenharmony_ci pa_modargs *ma = NULL; 24453a5a1b3Sopenharmony_ci struct userdata *u; 24553a5a1b3Sopenharmony_ci 24653a5a1b3Sopenharmony_ci pa_assert(m); 24753a5a1b3Sopenharmony_ci 24853a5a1b3Sopenharmony_ci if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 24953a5a1b3Sopenharmony_ci pa_log("Failed to parse module arguments"); 25053a5a1b3Sopenharmony_ci goto fail; 25153a5a1b3Sopenharmony_ci } 25253a5a1b3Sopenharmony_ci 25353a5a1b3Sopenharmony_ci u = pa_xnew0(struct userdata, 1); 25453a5a1b3Sopenharmony_ci u->rules = NULL; 25553a5a1b3Sopenharmony_ci m->userdata = u; 25653a5a1b3Sopenharmony_ci 25753a5a1b3Sopenharmony_ci u->property_key = pa_xstrdup(pa_modargs_get_value(ma, "key", PA_PROP_MEDIA_NAME)); 25853a5a1b3Sopenharmony_ci 25953a5a1b3Sopenharmony_ci if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0) 26053a5a1b3Sopenharmony_ci goto fail; 26153a5a1b3Sopenharmony_ci 26253a5a1b3Sopenharmony_ci /* hook EARLY - 1, to match before stream-restore */ 26353a5a1b3Sopenharmony_ci u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY - 1, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); 26453a5a1b3Sopenharmony_ci 26553a5a1b3Sopenharmony_ci pa_modargs_free(ma); 26653a5a1b3Sopenharmony_ci return 0; 26753a5a1b3Sopenharmony_ci 26853a5a1b3Sopenharmony_cifail: 26953a5a1b3Sopenharmony_ci pa__done(m); 27053a5a1b3Sopenharmony_ci 27153a5a1b3Sopenharmony_ci if (ma) 27253a5a1b3Sopenharmony_ci pa_modargs_free(ma); 27353a5a1b3Sopenharmony_ci return -1; 27453a5a1b3Sopenharmony_ci} 27553a5a1b3Sopenharmony_ci 27653a5a1b3Sopenharmony_civoid pa__done(pa_module*m) { 27753a5a1b3Sopenharmony_ci struct userdata* u; 27853a5a1b3Sopenharmony_ci struct rule *r, *n; 27953a5a1b3Sopenharmony_ci 28053a5a1b3Sopenharmony_ci pa_assert(m); 28153a5a1b3Sopenharmony_ci 28253a5a1b3Sopenharmony_ci if (!(u = m->userdata)) 28353a5a1b3Sopenharmony_ci return; 28453a5a1b3Sopenharmony_ci 28553a5a1b3Sopenharmony_ci if (u->sink_input_fixate_hook_slot) 28653a5a1b3Sopenharmony_ci pa_hook_slot_free(u->sink_input_fixate_hook_slot); 28753a5a1b3Sopenharmony_ci 28853a5a1b3Sopenharmony_ci if (u->property_key) 28953a5a1b3Sopenharmony_ci pa_xfree(u->property_key); 29053a5a1b3Sopenharmony_ci 29153a5a1b3Sopenharmony_ci for (r = u->rules; r; r = n) { 29253a5a1b3Sopenharmony_ci n = r->next; 29353a5a1b3Sopenharmony_ci 29453a5a1b3Sopenharmony_ci regfree(&r->regex); 29553a5a1b3Sopenharmony_ci if (r->proplist) 29653a5a1b3Sopenharmony_ci pa_proplist_free(r->proplist); 29753a5a1b3Sopenharmony_ci pa_xfree(r); 29853a5a1b3Sopenharmony_ci } 29953a5a1b3Sopenharmony_ci 30053a5a1b3Sopenharmony_ci pa_xfree(u); 30153a5a1b3Sopenharmony_ci} 302