Merge branch 'maint'
[paraslash.git] / mixer.c
1 /*
2 * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
3 *
4 * Licensed under the GPL v2. For licencing details see COPYING.
5 */
6
7 /** \file mixer.c A volume fader and alarm clock for OSS. */
8
9 #include <regex.h>
10 #include <lopsub.h>
11 #include <math.h>
12
13 #include "mixer.lsg.h"
14 #include "para.h"
15 #include "fd.h"
16 #include "string.h"
17 #include "mix.h"
18 #include "error.h"
19 #include "version.h"
20
21 /** Array of error strings. */
22 DEFINE_PARA_ERRLIST;
23
24 /* At least one of the two is defined if this file gets compiled. */
25 static const struct mixer *mixers[] = {
26 #ifdef HAVE_ALSA
27 &alsa_mixer,
28 #endif
29 #ifdef HAVE_OSS
30 &oss_mixer,
31 #endif
32 };
33
34 #define NUM_SUPPORTED_MIXERS (ARRAY_SIZE(mixers))
35 #define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++)
36
37 static struct lls_parse_result *lpr, *sub_lpr;
38
39 #define CMD_PTR(_cmd) (lls_cmd(LSG_MIXER_CMD_ ## _cmd, mixer_suite))
40 #define OPT_RESULT(_cmd, _opt) (lls_opt_result( \
41 LSG_MIXER_ ## _cmd ## _OPT_ ## _opt, (LSG_MIXER_CMD_ ## _cmd == 0)? lpr : sub_lpr))
42 #define OPT_GIVEN(_cmd, _opt) (lls_opt_given(OPT_RESULT(_cmd, _opt)))
43 #define OPT_STRING_VAL(_cmd, _opt) (lls_string_val(0, OPT_RESULT(_cmd, _opt)))
44 #define OPT_UINT32_VAL(_cmd, _opt) (lls_uint32_val(0, OPT_RESULT(_cmd, _opt)))
45
46 typedef int (*mixer_subcommand_handler_t)(const struct mixer *, struct mixer_handle *);
47
48 #define EXPORT_CMD(_cmd) const mixer_subcommand_handler_t \
49 lsg_mixer_com_ ## _cmd ## _user_data = &com_ ## _cmd;
50
51 static int loglevel;
52 static __printf_2_3 void date_log(int ll, const char *fmt, ...)
53 {
54 va_list argp;
55 time_t t1;
56 struct tm *tm;
57
58 if (ll < loglevel)
59 return;
60 time(&t1);
61 tm = localtime(&t1);
62 fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
63 va_start(argp, fmt);
64 vprintf(fmt, argp);
65 va_end(argp);
66 }
67 __printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
68
69 static int set_channel(const struct mixer *m, struct mixer_handle *h,
70 const char *channel)
71 {
72
73 PARA_NOTICE_LOG("using %s mixer channel\n", channel?
74 channel : "default");
75 return m->set_channel(h, channel);
76 }
77
78 static void millisleep(int ms)
79 {
80 struct timespec ts;
81
82 PARA_INFO_LOG("sleeping %dms\n", ms);
83 if (ms < 0)
84 return;
85 ts.tv_sec = ms / 1000,
86 ts.tv_nsec = (ms % 1000) * 1000 * 1000;
87 nanosleep(&ts, NULL);
88 }
89
90 /*
91 * This implements the inverse function of t -> t^alpha, scaled to the time
92 * interval [0,T] and the range given by old_vol and new_vol. It returns the
93 * amount of milliseconds until the given volume is reached.
94 */
95 static unsigned volume_time(double vol, double old_vol, double new_vol,
96 double T, double alpha)
97 {
98 double c, d, x;
99
100 if (old_vol < new_vol) {
101 c = old_vol;
102 d = new_vol;
103 } else {
104 c = new_vol;
105 d = old_vol;
106 }
107
108 x = T * exp(log(((vol - c) / (d - c))) / alpha);
109 assert(x <= T);
110 if (old_vol < new_vol)
111 return x;
112 else
113 return T - x;
114 }
115
116 /* Fade to new volume in fade_time seconds. */
117 static int fade(const struct mixer *m, struct mixer_handle *h, uint32_t new_vol,
118 uint32_t fade_time)
119 {
120 int i, T, old_vol, ret, slept, incr;
121 double ms, alpha;
122 uint32_t fe = OPT_UINT32_VAL(PARA_MIXER, FADE_EXPONENT);
123
124 if (fade_time <= 0 || fe >= 100) {
125 ret = m->set(h, new_vol);
126 if (ret < 0)
127 return ret;
128 goto sleep;
129 }
130 alpha = (100 - fe) / 100.0;
131 ret = m->get(h);
132 if (ret < 0)
133 return ret;
134 old_vol = ret;
135 if (old_vol == new_vol)
136 goto sleep;
137 PARA_NOTICE_LOG("fading %s from %d to %u in %u seconds\n",
138 OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL), old_vol,
139 new_vol, fade_time);
140 incr = old_vol < new_vol? 1 : -1;
141 T = fade_time * 1000;
142 i = old_vol;
143 slept = 0;
144 do {
145 ms = volume_time(i + incr, old_vol, new_vol, T, alpha);
146 millisleep(ms - slept);
147 slept = ms;
148 i += incr;
149 ret = m->set(h, i);
150 if (ret < 0)
151 return ret;
152 } while (i != new_vol);
153 return 1;
154 sleep:
155 sleep(fade_time);
156 return ret;
157 }
158
159 static int com_fade(const struct mixer *m, struct mixer_handle *h)
160 {
161 uint32_t new_vol = OPT_UINT32_VAL(FADE, FADE_VOL);
162 uint32_t fade_time = OPT_UINT32_VAL(FADE, FADE_TIME);
163 return fade(m, h, new_vol, fade_time);
164 }
165 EXPORT_CMD(fade);
166
167 static void client_cmd(const char *cmd)
168 {
169 int ret, status, fds[3] = {0, 0, 0};
170 pid_t pid;
171 char *cmdline = make_message(BINDIR "/para_client %s", cmd);
172
173 PARA_NOTICE_LOG("%s\n", cmdline);
174 ret = para_exec_cmdline_pid(&pid, cmdline, fds);
175 free(cmdline);
176 if (ret < 0) {
177 PARA_ERROR_LOG("%s\n", para_strerror(-ret));
178 goto fail;
179 }
180 do
181 pid = waitpid(pid, &status, 0);
182 while (pid == -1 && errno == EINTR);
183 if (pid < 0) {
184 PARA_ERROR_LOG("%s\n", strerror(errno));
185 goto fail;
186 }
187 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
188 goto fail;
189 return;
190 fail:
191 PARA_EMERG_LOG("command \"%s\" failed\n", cmd);
192 exit(EXIT_FAILURE);
193 }
194
195 static void change_afs_mode(const char *afs_mode)
196 {
197 char *cmd;
198
199 client_cmd("stop");
200 if (!afs_mode)
201 return;
202 cmd = make_message("select %s", afs_mode);
203 client_cmd(cmd);
204 free(cmd);
205 }
206
207 static int set_initial_volume(const struct mixer *m, struct mixer_handle *h)
208 {
209 int i, ret;
210
211 for (i = 0; i < OPT_GIVEN(SLEEP, IVOL); i++) {
212 const char *val = lls_string_val(i, OPT_RESULT(SLEEP, IVOL));
213 char *p, *ch, *arg = para_strdup(val);
214 int32_t iv;
215 p = strchr(arg, ':');
216 if (p) {
217 *p = '\0';
218 p++;
219 ch = arg;
220 } else {
221 p = arg;
222 ch = NULL;
223 }
224 ret = para_atoi32(p, &iv);
225 if (ret < 0) {
226 free(arg);
227 return ret;
228 }
229 ret = set_channel(m, h, ch);
230 if (!ch)
231 ch = "default";
232 if (ret < 0) {
233 PARA_WARNING_LOG("ignoring channel %s\n", ch);
234 ret = 0;
235 } else {
236 PARA_INFO_LOG("initial volume %s: %d\n", ch, iv);
237 ret = m->set(h, iv);
238 }
239 free(arg);
240 if (ret < 0)
241 return ret;
242 }
243 return 1;
244 }
245
246 static int com_sleep(const struct mixer *m, struct mixer_handle *h)
247 {
248 time_t t1, wake_time_epoch;
249 unsigned int delay;
250 struct tm *tm;
251 int ret;
252 const char *wake_time = OPT_STRING_VAL(SLEEP, WAKE_TIME);
253 const char *fo_mood = OPT_STRING_VAL(SLEEP, FO_MOOD);
254 const char *fi_mood = OPT_STRING_VAL(SLEEP, FI_MOOD);
255 const char *sleep_mood = OPT_STRING_VAL(SLEEP, SLEEP_MOOD);
256 int fit = OPT_UINT32_VAL(SLEEP, FI_TIME);
257 int fot = OPT_UINT32_VAL(SLEEP, FO_TIME);
258 int fiv = OPT_UINT32_VAL(SLEEP, FI_VOL);
259 int fov = OPT_UINT32_VAL(SLEEP, FO_VOL);
260 int32_t hour, min = 0;
261 char *tmp;
262 char *wt = para_strdup(wake_time + (wake_time[0] == '+'));
263
264 /* calculate wake time */
265 time(&t1);
266 tmp = strchr(wt, ':');
267 if (tmp) {
268 *tmp = '\0';
269 tmp++;
270 ret = para_atoi32(tmp, &min);
271 if (ret < 0) {
272 free(wt);
273 return ret;
274 }
275 }
276 ret = para_atoi32(wt, &hour);
277 free(wt);
278 if (ret < 0)
279 return ret;
280 if (wake_time[0] == '+') { /* relative */
281 t1 += hour * 60 * 60 + min * 60;
282 tm = localtime(&t1);
283 } else {
284 tm = localtime(&t1);
285 if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
286 t1 += 86400; /* wake time is tomorrow */
287 tm = localtime(&t1);
288 }
289 tm->tm_hour = hour;
290 tm->tm_min = min;
291 tm->tm_sec = 0;
292 }
293 wake_time_epoch = mktime(tm);
294 PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
295 client_cmd("stop");
296 sleep(1);
297 if (fot) {
298 ret = set_initial_volume(m, h);
299 if (ret < 0)
300 return ret;
301 change_afs_mode(fo_mood);
302 client_cmd("play");
303 ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
304 if (ret < 0)
305 return ret;
306 ret = fade(m, h, fov, fot);
307 if (ret < 0)
308 return ret;
309 } else {
310 ret = m->set(h, fov);
311 if (ret < 0)
312 return ret;
313 }
314 if (OPT_GIVEN(SLEEP, SLEEP_MOOD)) {
315 change_afs_mode(sleep_mood);
316 client_cmd("play");
317 } else
318 client_cmd("stop");
319 if (!fit)
320 return 1;
321 change_afs_mode(fi_mood);
322 for (;;) {
323 time(&t1);
324 if (wake_time_epoch <= t1 + fit)
325 break;
326 delay = wake_time_epoch - t1 - fit;
327 PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
328 delay, delay / 3600,
329 (delay % 3600) / 60);
330 sleep(delay);
331 }
332 client_cmd("play");
333 ret = fade(m, h, fiv, fit);
334 PARA_INFO_LOG("fade complete, returning\n");
335 return ret;
336 }
337 EXPORT_CMD(sleep);
338
339 static int com_snooze(const struct mixer *m, struct mixer_handle *h)
340 {
341 int ret, val;
342
343 if (OPT_UINT32_VAL(SNOOZE, SO_TIME) == 0)
344 return 1;
345 ret = m->get(h);
346 if (ret < 0)
347 return ret;
348 val = ret;
349 if (val < OPT_UINT32_VAL(SNOOZE, SO_VOL))
350 ret = m->set(h, OPT_UINT32_VAL(SNOOZE, SO_VOL));
351 else
352 ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SO_VOL),
353 OPT_UINT32_VAL(SNOOZE, SO_TIME));
354 if (ret < 0)
355 return ret;
356 client_cmd("pause");
357 PARA_NOTICE_LOG("%" PRIu32 " seconds snooze time...\n",
358 OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
359 sleep(OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
360 client_cmd("play");
361 return fade(m, h, OPT_UINT32_VAL(SNOOZE, SI_VOL),
362 OPT_UINT32_VAL(SNOOZE, SI_TIME));
363 }
364 EXPORT_CMD(snooze);
365
366 static int com_set(const struct mixer *m, struct mixer_handle *h)
367 {
368 return m->set(h, OPT_UINT32_VAL(SET, VAL));
369 }
370 EXPORT_CMD(set);
371
372 static const struct mixer *get_mixer_or_die(void)
373 {
374 int i;
375
376 if (!OPT_GIVEN(PARA_MIXER, MIXER_API))
377 i = 0; /* default: use first mixer */
378 else
379 FOR_EACH_MIXER(i)
380 if (!strcmp(mixers[i]->name,
381 OPT_STRING_VAL(PARA_MIXER, MIXER_API)))
382 break;
383 if (i < NUM_SUPPORTED_MIXERS) {
384 PARA_NOTICE_LOG("using %s mixer API\n", mixers[i]->name);
385 return mixers[i];
386 }
387 printf("available mixer APIs: ");
388 FOR_EACH_MIXER(i)
389 printf("%s%s%s ", i == 0? "[" : "", mixers[i]->name,
390 i == 0? "]" : "");
391 printf("\n");
392 exit(EXIT_FAILURE);
393 }
394
395 static void show_subcommands(void)
396 {
397 const struct lls_command *cmd;
398 int i;
399 printf("Subcommands:\n");
400 for (i = 1; (cmd = lls_cmd(i, mixer_suite)); i++) {
401 const char *name = lls_command_name(cmd);
402 const char *purpose = lls_purpose(cmd);
403 printf("%-20s%s\n", name, purpose);
404 }
405 }
406
407
408 static int com_help(__a_unused const struct mixer *m,
409 __a_unused struct mixer_handle *h)
410 {
411 const struct lls_command *cmd;
412 const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG);
413 char *txt, *errctx;
414 const char *name;
415 int ret;
416
417 ret = lls_check_arg_count(sub_lpr, 0, 1, NULL);
418 if (ret < 0)
419 return ret;
420 if (lls_num_inputs(sub_lpr) == 0) {
421 show_subcommands();
422 return 0;
423 }
424 name = lls_input(0, sub_lpr);
425 ret = lls(lls_lookup_subcmd(name, mixer_suite, &errctx));
426 if (ret < 0) {
427 if (errctx)
428 PARA_ERROR_LOG("%s\n", errctx);
429 free(errctx);
430 return ret;
431 }
432 cmd = lls_cmd(ret, mixer_suite);
433 if (lls_opt_given(r_l))
434 txt = lls_long_help(cmd);
435 else
436 txt = lls_short_help(cmd);
437 printf("%s", txt);
438 free(txt);
439 return 0;
440 }
441 EXPORT_CMD(help);
442
443 static void handle_help_flags(void)
444 {
445 char *help;
446
447 if (OPT_GIVEN(PARA_MIXER, DETAILED_HELP))
448 help = lls_long_help(CMD_PTR(PARA_MIXER));
449 else if (OPT_GIVEN(PARA_MIXER, HELP))
450 help = lls_short_help(CMD_PTR(PARA_MIXER));
451 else
452 return;
453 printf("%s", help);
454 free(help);
455 show_subcommands();
456 exit(EXIT_SUCCESS);
457 }
458
459 static int parse_and_merge_config_file(const struct lls_command *cmd)
460 {
461 int ret;
462 int cf_argc;
463 char **cf_argv;
464 char *cf, *errctx = NULL;
465 struct lls_parse_result **lprp, *cf_lpr, *merged_lpr;
466 void *map;
467 size_t sz;
468 const char *subcmd_name;
469
470 if (cmd == lls_cmd(0, mixer_suite)) {
471 lprp = &lpr;
472 subcmd_name = NULL;
473 } else {
474 lprp = &sub_lpr;
475 subcmd_name = lls_command_name(cmd);
476 }
477 if (OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
478 cf = para_strdup(OPT_STRING_VAL(PARA_MIXER, CONFIG_FILE));
479 else {
480 char *home = para_homedir();
481 cf = make_message("%s/.paraslash/mixer.conf", home);
482 free(home);
483 }
484 ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
485 if (ret < 0) {
486 if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
487 goto free_cf;
488 if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
489 OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
490 goto free_cf;
491 } else {
492 ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
493 para_munmap(map, sz);
494 if (ret < 0)
495 goto free_cf;
496 cf_argc = ret;
497 ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
498 lls_free_argv(cf_argv);
499 if (ret < 0)
500 goto free_cf;
501 ret = lls(lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx));
502 lls_free_parse_result(cf_lpr, cmd);
503 if (ret < 0)
504 goto free_cf;
505 lls_free_parse_result(*lprp, cmd);
506 *lprp = merged_lpr;
507 loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
508 }
509 ret = 1;
510 free_cf:
511 free(cf);
512 if (errctx)
513 PARA_ERROR_LOG("%s\n", errctx);
514 free(errctx);
515 return ret;
516 }
517
518 /**
519 * The main function of para_mixer.
520 *
521 * The executable is linked with the alsa or the oss mixer API, or both. It has
522 * a custom log function which prefixes log messages with the current date.
523 *
524 * \param argc Argument counter.
525 * \param argv Argument vector.
526 *
527 * \return EXIT_SUCCESS or EXIT_FAILURE.
528 */
529 int main(int argc, char *argv[])
530 {
531 const struct lls_command *cmd = CMD_PTR(PARA_MIXER);
532 int ret;
533 char *errctx;
534 const char *subcmd;
535 const struct mixer *m;
536 struct mixer_handle *h;
537 unsigned n;
538
539 ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
540 if (ret < 0)
541 goto fail;
542 loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
543 version_handle_flag("mixer", OPT_GIVEN(PARA_MIXER, VERSION));
544 handle_help_flags();
545
546 n = lls_num_inputs(lpr);
547 if (n == 0) {
548 show_subcommands();
549 ret = 0;
550 goto free_lpr;
551 }
552 ret = parse_and_merge_config_file(cmd);
553 if (ret < 0)
554 goto free_lpr;
555 subcmd = lls_input(0, lpr);
556 ret = lls(lls_lookup_subcmd(subcmd, mixer_suite, &errctx));
557 if (ret < 0)
558 goto fail;
559 cmd = lls_cmd(ret, mixer_suite);
560 ret = lls(lls_parse(n, argv + argc - n, cmd, &sub_lpr, &errctx));
561 if (ret < 0)
562 goto free_lpr;
563 ret = parse_and_merge_config_file(cmd);
564 if (ret < 0)
565 goto free_lpr;
566 m = get_mixer_or_die();
567 ret = m->open(OPT_STRING_VAL(PARA_MIXER, MIXER_DEVICE), &h);
568 if (ret < 0)
569 goto free_sub_lpr;
570 ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
571 if (ret == -E_BAD_CHANNEL) {
572 char *channels = m->get_channels(h);
573 printf("Available channels: %s\n", channels);
574 free(channels);
575 }
576 if (ret < 0)
577 goto close_mixer;
578 ret = (*(mixer_subcommand_handler_t *)(lls_user_data(cmd)))(m ,h);
579 close_mixer:
580 m->close(&h);
581 free_sub_lpr:
582 lls_free_parse_result(sub_lpr, cmd);
583 free_lpr:
584 lls_free_parse_result(lpr, CMD_PTR(PARA_MIXER));
585 if (ret >= 0)
586 return EXIT_SUCCESS;
587 fail:
588 if (errctx)
589 PARA_ERROR_LOG("%s\n", errctx);
590 free(errctx);
591 PARA_EMERG_LOG("%s\n", para_strerror(-ret));
592 return EXIT_FAILURE;
593 }