1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2004-2006 Lennart Poettering 5 6 PulseAudio is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published 8 by the Free Software Foundation; either version 2.1 of the License, 9 or (at your option) any later version. 10 11 PulseAudio is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <unistd.h> 25#include <string.h> 26#include <errno.h> 27#include <sys/types.h> 28#include <stdio.h> 29#include <stdlib.h> 30 31#if defined(HAVE_REGEX_H) 32#include <regex.h> 33#elif defined(HAVE_PCREPOSIX_H) 34#include <pcreposix.h> 35#endif 36 37#include <pulse/xmalloc.h> 38 39#include <pulsecore/core-error.h> 40#include <pulsecore/module.h> 41#include <pulsecore/core-util.h> 42#include <pulsecore/modargs.h> 43#include <pulsecore/log.h> 44#include <pulsecore/sink-input.h> 45#include <pulsecore/core-util.h> 46 47PA_MODULE_AUTHOR("Lennart Poettering"); 48PA_MODULE_DESCRIPTION("Playback stream expression matching module"); 49PA_MODULE_VERSION(PACKAGE_VERSION); 50PA_MODULE_LOAD_ONCE(false); 51PA_MODULE_USAGE("table=<filename> " 52 "key=<property_key>"); 53 54#define WHITESPACE "\n\r \t" 55 56#define DEFAULT_MATCH_TABLE_FILE PA_DEFAULT_CONFIG_DIR"/match.table" 57#define DEFAULT_MATCH_TABLE_FILE_USER "match.table" 58 59#define UPDATE_REPLACE "replace" 60#define UPDATE_MERGE "merge" 61 62static const char* const valid_modargs[] = { 63 "table", 64 "key", 65 NULL, 66}; 67 68struct rule { 69 regex_t regex; 70 pa_volume_t volume; 71 pa_proplist *proplist; 72 pa_update_mode_t mode; 73 struct rule *next; 74}; 75 76struct userdata { 77 struct rule *rules; 78 char *property_key; 79 pa_hook_slot *sink_input_fixate_hook_slot; 80}; 81 82static int load_rules(struct userdata *u, const char *filename) { 83 FILE *f; 84 int n = 0; 85 int ret = -1; 86 struct rule *end = NULL; 87 char *fn = NULL; 88 89 pa_assert(u); 90 91 if (filename) 92 f = pa_fopen_cloexec(fn = pa_xstrdup(filename), "r"); 93 else 94 f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn); 95 96 if (!f) { 97 pa_log("Failed to open file config file: %s", pa_cstrerror(errno)); 98 goto finish; 99 } 100 101 pa_lock_fd(fileno(f), 1); 102 103 while (!feof(f)) { 104 char *token_end, *value_str; 105 pa_volume_t volume = PA_VOLUME_NORM; 106 uint32_t k; 107 regex_t regex; 108 char ln[256]; 109 struct rule *rule; 110 pa_proplist *proplist = NULL; 111 pa_update_mode_t mode = (pa_update_mode_t) -1; 112 113 if (!fgets(ln, sizeof(ln), f)) 114 break; 115 116 n++; 117 118 pa_strip_nl(ln); 119 120 if (ln[0] == '#' || !*ln ) 121 continue; 122 123 token_end = ln + strcspn(ln, WHITESPACE); 124 value_str = token_end + strspn(token_end, WHITESPACE); 125 *token_end = 0; 126 127 if (!*ln) { 128 pa_log("[%s:%u] failed to parse line - missing regexp", fn, n); 129 goto finish; 130 } 131 132 if (!*value_str) { 133 pa_log("[%s:%u] failed to parse line - too few words", fn, n); 134 goto finish; 135 } 136 137 if (pa_atou(value_str, &k) >= 0) 138 volume = (pa_volume_t) PA_CLAMP_VOLUME(k); 139 else { 140 size_t len; 141 142 token_end = value_str + strcspn(value_str, WHITESPACE); 143 144 len = token_end - value_str; 145 if (len == (sizeof(UPDATE_REPLACE) - 1) && !strncmp(value_str, UPDATE_REPLACE, len)) 146 mode = PA_UPDATE_REPLACE; 147 else if (len == (sizeof(UPDATE_MERGE) - 1) && !strncmp(value_str, UPDATE_MERGE, len)) 148 mode = PA_UPDATE_MERGE; 149 150 if (mode != (pa_update_mode_t) -1) { 151 value_str = token_end + strspn(token_end, WHITESPACE); 152 153 if (!*value_str) { 154 pa_log("[%s:%u] failed to parse line - too few words", fn, n); 155 goto finish; 156 } 157 } else 158 mode = PA_UPDATE_MERGE; 159 160 if (*value_str == '"') { 161 value_str++; 162 163 token_end = strchr(value_str, '"'); 164 if (!token_end) { 165 pa_log("[%s:%u] failed to parse line - missing role closing quote", fn, n); 166 goto finish; 167 } 168 } else 169 token_end = value_str + strcspn(value_str, WHITESPACE); 170 171 *token_end = 0; 172 173 value_str = pa_sprintf_malloc("media.role=\"%s\"", value_str); 174 proplist = pa_proplist_from_string(value_str); 175 pa_xfree(value_str); 176 } 177 178 if (regcomp(®ex, ln, REG_EXTENDED|REG_NOSUB) != 0) { 179 pa_log("[%s:%u] invalid regular expression", fn, n); 180 if (proplist) 181 pa_proplist_free(proplist); 182 goto finish; 183 } 184 185 rule = pa_xnew(struct rule, 1); 186 rule->regex = regex; 187 rule->proplist = proplist; 188 rule->mode = mode; 189 rule->volume = volume; 190 rule->next = NULL; 191 192 if (end) 193 end->next = rule; 194 else 195 u->rules = rule; 196 end = rule; 197 } 198 199 ret = 0; 200 201finish: 202 if (f) { 203 pa_lock_fd(fileno(f), 0); 204 fclose(f); 205 } 206 207 pa_xfree(fn); 208 209 return ret; 210} 211 212static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *si, struct userdata *u) { 213 struct rule *r; 214 const char *n; 215 216 pa_assert(c); 217 pa_assert(u); 218 219 if (!(n = pa_proplist_gets(si->proplist, u->property_key))) 220 return PA_HOOK_OK; 221 222 pa_log_debug("Matching with %s", n); 223 224 for (r = u->rules; r; r = r->next) { 225 if (!regexec(&r->regex, n, 0, NULL, 0)) { 226 if (r->proplist) { 227 pa_log_debug("updating proplist of sink input '%s'", n); 228 pa_proplist_update(si->proplist, r->mode, r->proplist); 229 } else if (si->volume_writable) { 230 pa_cvolume cv; 231 pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume); 232 pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); 233 pa_sink_input_new_data_set_volume(si, &cv); 234 } else 235 pa_log_debug("the volume of sink input '%s' is not writable, can't change it", n); 236 } 237 } 238 239 return PA_HOOK_OK; 240} 241 242int pa__init(pa_module*m) { 243 pa_modargs *ma = NULL; 244 struct userdata *u; 245 246 pa_assert(m); 247 248 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 249 pa_log("Failed to parse module arguments"); 250 goto fail; 251 } 252 253 u = pa_xnew0(struct userdata, 1); 254 u->rules = NULL; 255 m->userdata = u; 256 257 u->property_key = pa_xstrdup(pa_modargs_get_value(ma, "key", PA_PROP_MEDIA_NAME)); 258 259 if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0) 260 goto fail; 261 262 /* hook EARLY - 1, to match before stream-restore */ 263 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); 264 265 pa_modargs_free(ma); 266 return 0; 267 268fail: 269 pa__done(m); 270 271 if (ma) 272 pa_modargs_free(ma); 273 return -1; 274} 275 276void pa__done(pa_module*m) { 277 struct userdata* u; 278 struct rule *r, *n; 279 280 pa_assert(m); 281 282 if (!(u = m->userdata)) 283 return; 284 285 if (u->sink_input_fixate_hook_slot) 286 pa_hook_slot_free(u->sink_input_fixate_hook_slot); 287 288 if (u->property_key) 289 pa_xfree(u->property_key); 290 291 for (r = u->rules; r; r = n) { 292 n = r->next; 293 294 regfree(&r->regex); 295 if (r->proplist) 296 pa_proplist_free(r->proplist); 297 pa_xfree(r); 298 } 299 300 pa_xfree(u); 301} 302