make converter_arg in resamplefilter an enum option
[paraslash.git] / fade.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 fade.c A volume fader and alarm clock for OSS. */
8
9 #include <regex.h>
10 #include <lopsub.h>
11
12 #include "fade.lsg.h"
13 #include "para.h"
14 #include "fd.h"
15 #include "string.h"
16 #include "mix.h"
17 #include "error.h"
18 #include "version.h"
19
20 /** Array of error strings. */
21 DEFINE_PARA_ERRLIST;
22
23 enum mixer_id {MIXER_ENUM};
24 static char *mixer_name[] = {MIXER_NAMES};
25 DECLARE_MIXER_INITS;
26 static struct mixer supported_mixer[] = {MIXER_ARRAY};
27 #define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++)
28
29 static struct lls_parse_result *lpr;
30
31 #define CMD_PTR (lls_cmd(0, fade_suite))
32 #define OPT_RESULT(_name) (lls_opt_result(LSG_FADE_PARA_FADE_OPT_ ## _name, lpr))
33 #define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
34 #define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
35 #define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
36
37 static int loglevel;
38 static __printf_2_3 void date_log(int ll, const char *fmt, ...)
39 {
40         va_list argp;
41         time_t t1;
42         struct tm *tm;
43
44         if (ll < loglevel)
45                 return;
46         time(&t1);
47         tm = localtime(&t1);
48         fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
49         va_start(argp, fmt);
50         vprintf(fmt, argp);
51         va_end(argp);
52 }
53 __printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
54
55 static int set_channel(struct mixer *m, struct mixer_handle *h, const char *channel)
56 {
57
58         PARA_NOTICE_LOG("using %s mixer channel\n", channel?
59                 channel : "default");
60         return m->set_channel(h, channel);
61 }
62
63 /* Fade to new volume in fade_time seconds. */
64 static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_time)
65 {
66         int vol, diff, incr, ret;
67         unsigned secs;
68         struct timespec ts;
69         unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
70
71         if (fade_time <= 0)
72                 return m->set(h, new_vol);
73         secs = fade_time;
74         ret = m->get(h);
75         if (ret < 0)
76                 goto out;
77         vol = ret;
78         PARA_NOTICE_LOG("fading %s from %d to %d in %u seconds\n",
79                 OPT_STRING_VAL(MIXER_CHANNEL), vol, new_vol, secs);
80         diff = new_vol - vol;
81         if (!diff) {
82                 sleep(secs);
83                 goto out;
84         }
85         incr = diff > 0? 1: -1;
86         diff = diff > 0? diff: -diff;
87         tmp = secs * 1000 / diff;
88         tmp2 = tmp % 1000;
89         while ((new_vol - vol) * incr > 0) {
90                 ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/
91                 ts.tv_sec = tmp / 1000; /* really nec ?*/
92                 //printf("ts.tv_sec: %i\n", ts.tv_nsec);
93                 vol += incr;
94                 ret = m->set(h, vol);
95                 if (ret < 0)
96                         goto out;
97                 //printf("vol = %i\n", vol);
98                 nanosleep(&ts, NULL);
99         }
100 out:
101         return ret;
102 }
103
104 static void client_cmd(const char *cmd)
105 {
106         int ret, status, fds[3] = {0, 0, 0};
107         pid_t pid;
108         char *cmdline = make_message(BINDIR "/para_client %s", cmd);
109
110         PARA_NOTICE_LOG("%s\n", cmdline);
111         ret = para_exec_cmdline_pid(&pid, cmdline, fds);
112         free(cmdline);
113         if (ret < 0) {
114                 PARA_ERROR_LOG("%s\n", para_strerror(-ret));
115                 goto fail;
116         }
117         do
118                 pid = waitpid(pid, &status, 0);
119         while (pid == -1 && errno == EINTR);
120         if (pid < 0) {
121                 PARA_ERROR_LOG("%s\n", strerror(errno));
122                 goto fail;
123         }
124         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
125                 goto fail;
126         return;
127 fail:
128         PARA_EMERG_LOG("command \"%s\" failed\n", cmd);
129         exit(EXIT_FAILURE);
130 }
131
132 static void change_afs_mode(const char *afs_mode)
133 {
134         char *cmd;
135
136         client_cmd("stop");
137         if (!afs_mode)
138                 return;
139         cmd = make_message("select %s", afs_mode);
140         client_cmd(cmd);
141         free(cmd);
142 }
143
144 static int set_initial_volume(struct mixer *m, struct mixer_handle *h)
145 {
146         int i, ret;
147
148         for (i = 0; i < OPT_GIVEN(IVOL); i++) {
149                 const char *val = lls_string_val(i, OPT_RESULT(IVOL));
150                 char *p, *ch, *arg = para_strdup(val);
151                 int32_t iv;
152                 p = strchr(arg, ':');
153                 if (p) {
154                         *p = '\0';
155                         p++;
156                         ch = arg;
157                 } else {
158                         p = arg;
159                         ch = NULL;
160                 }
161                 ret = para_atoi32(p, &iv);
162                 if (ret < 0) {
163                         free(arg);
164                         return ret;
165                 }
166                 ret = set_channel(m, h, ch);
167                 if (!ch)
168                         ch = "default";
169                 if (ret < 0) {
170                         PARA_WARNING_LOG("ignoring channel %s\n", ch);
171                         ret = 0;
172                 } else {
173                         PARA_INFO_LOG("initial volume %s: %d\n", ch, iv);
174                         ret = m->set(h, iv);
175                 }
176                 free(arg);
177                 if (ret < 0)
178                         return ret;
179         }
180         return 1;
181 }
182
183 static int sweet_dreams(struct mixer *m, struct mixer_handle *h)
184 {
185         time_t t1, wake_time_epoch;
186         unsigned int delay;
187         struct tm *tm;
188         int ret, min = OPT_UINT32_VAL(WAKE_MIN);
189         const char *fo_mood = OPT_STRING_VAL(FO_MOOD);
190         const char *fi_mood = OPT_STRING_VAL(FI_MOOD);
191         const char *sleep_mood = OPT_STRING_VAL(SLEEP_MOOD);
192         int fit = OPT_UINT32_VAL(FI_TIME);
193         int fot = OPT_UINT32_VAL(FO_TIME);
194         int fiv = OPT_UINT32_VAL(FI_VOL);
195         int fov = OPT_UINT32_VAL(FO_VOL);
196
197         /* calculate wake time */
198         time(&t1);
199         if (OPT_GIVEN(WAKE_HOUR)) {
200                 int hour = OPT_UINT32_VAL(WAKE_HOUR);
201                 tm = localtime(&t1);
202                 if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
203                         t1 += 86400; /* wake time is tomorrow */
204                         tm = localtime(&t1);
205                 }
206                 tm->tm_hour = hour;
207                 tm->tm_min = min;
208                 tm->tm_sec = 0;
209         } else {
210                 t1 += 9 * 60 * 60; /* nine hours from now */
211                 PARA_INFO_LOG("default wake time: %lu\n", (long unsigned)t1);
212                 tm = localtime(&t1);
213         }
214         wake_time_epoch = mktime(tm);
215         PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
216         client_cmd("stop");
217         sleep(1);
218         if (fot) {
219                 ret = set_initial_volume(m, h);
220                 if (ret < 0)
221                         return ret;
222                 change_afs_mode(fo_mood);
223                 client_cmd("play");
224                 ret = set_channel(m, h, OPT_STRING_VAL(MIXER_CHANNEL));
225                 if (ret < 0)
226                         return ret;
227                 ret = fade(m, h, fov, fot);
228                 if (ret < 0)
229                         return ret;
230         } else {
231                 ret = m->set(h, fov);
232                 if (ret < 0)
233                         return ret;
234         }
235         if (OPT_GIVEN(SLEEP_MOOD)) {
236                 change_afs_mode(sleep_mood);
237                 client_cmd("play");
238         } else
239                 client_cmd("stop");
240         if (!fit)
241                 return 1;
242         change_afs_mode(fi_mood);
243         for (;;) {
244                 time(&t1);
245                 if (wake_time_epoch <= t1 + fit)
246                         break;
247                 delay = wake_time_epoch - t1 - fit;
248                 PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
249                         delay, delay / 3600,
250                         (delay % 3600) / 60);
251                 sleep(delay);
252         }
253         client_cmd("play");
254         ret = fade(m, h, fiv, fit);
255         PARA_INFO_LOG("fade complete, returning\n");
256         return ret;
257 }
258
259 static int snooze(struct mixer *m, struct mixer_handle *h)
260 {
261         int ret, val;
262
263         if (OPT_UINT32_VAL(SO_TIME) == 0)
264                 return 1;
265         ret = m->get(h);
266         if (ret < 0)
267                 return ret;
268         val = ret;
269         if (val < OPT_UINT32_VAL(SO_VOL))
270                 ret = m->set(h, OPT_UINT32_VAL(SO_VOL));
271         else
272                 ret = fade(m, h, OPT_UINT32_VAL(SO_VOL),
273                         OPT_UINT32_VAL(SO_TIME));
274         if (ret < 0)
275                 return ret;
276         client_cmd("pause");
277         PARA_NOTICE_LOG("%" PRIu32 " seconds snooze time...\n",
278                 OPT_UINT32_VAL(SNOOZE_TIME));
279         sleep(OPT_UINT32_VAL(SNOOZE_TIME));
280         client_cmd("play");
281         return fade(m, h, OPT_UINT32_VAL(SI_VOL), OPT_UINT32_VAL(SI_TIME));
282 }
283
284 static void init_mixers(void)
285 {
286         int i;
287
288         FOR_EACH_MIXER(i) {
289                 struct mixer *m = &supported_mixer[i];
290                 PARA_DEBUG_LOG("initializing mixer API #%d (%s)\n",
291                         i, mixer_name[i]);
292                 m->init(m);
293         }
294 }
295
296 static int set_val(struct mixer *m, struct mixer_handle *h)
297 {
298         return m->set(h, OPT_UINT32_VAL(VAL));
299 }
300
301 static struct mixer *get_mixer_or_die(void)
302 {
303         int i;
304
305         if (!OPT_GIVEN(MIXER_API))
306                 i = DEFAULT_MIXER;
307         else
308                 FOR_EACH_MIXER(i)
309                         if (!strcmp(mixer_name[i], OPT_STRING_VAL(MIXER_API)))
310                                 break;
311         if (i < NUM_SUPPORTED_MIXERS) {
312                 PARA_NOTICE_LOG("using %s mixer API\n", mixer_name[i]);
313                 return supported_mixer + i;
314         }
315         printf("available mixer APIs: ");
316         FOR_EACH_MIXER(i) {
317                 int d = (i == DEFAULT_MIXER);
318                 printf("%s%s%s ", d? "[" : "", mixer_name[i], d? "]" : "");
319         }
320         printf("\n");
321         exit(EXIT_FAILURE);
322 }
323
324 static void handle_help_flags(void)
325 {
326         char *help;
327
328         if (OPT_GIVEN(DETAILED_HELP))
329                 help = lls_long_help(CMD_PTR);
330         else if (OPT_GIVEN(HELP))
331                 help = lls_short_help(CMD_PTR);
332         else
333                 return;
334         printf("%s\n", help);
335         free(help);
336         exit(EXIT_SUCCESS);
337 }
338
339 /**
340  * The main function of para_fade.
341  *
342  * The executable is linked with the alsa or the oss mixer API, or both. It has
343  * a custom log function which prefixes log messages with the current date.
344  *
345  * \param argc Argument counter.
346  * \param argv Argument vector.
347  *
348  * \return EXIT_SUCCESS or EXIT_FAILURE.
349  */
350 int main(int argc, char *argv[])
351 {
352         const struct lls_command *cmd = CMD_PTR;
353         int ret;
354         char *cf, *errctx;
355         struct mixer *m;
356         struct mixer_handle *h;
357         void *map;
358         size_t sz;
359
360         ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
361         if (ret < 0)
362                 goto fail;
363         loglevel = OPT_UINT32_VAL(LOGLEVEL);
364         version_handle_flag("fade", OPT_GIVEN(VERSION));
365         handle_help_flags();
366
367         if (OPT_GIVEN(CONFIG_FILE))
368                 cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
369         else {
370                 char *home = para_homedir();
371                 cf = make_message("%s/.paraslash/fade.conf", home);
372                 free(home);
373         }
374         ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
375         if (ret < 0) {
376                 if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
377                         goto free_cf;
378                 if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
379                         goto free_cf;
380         } else {
381                 int cf_argc;
382                 char **cf_argv;
383                 struct lls_parse_result *cf_lpr, *merged_lpr;
384                 ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
385                 para_munmap(map, sz);
386                 if (ret < 0)
387                         goto free_cf;
388                 cf_argc = ret;
389                 ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
390                 lls_free_argv(cf_argv);
391                 if (ret < 0)
392                         goto free_cf;
393                 ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr,
394                         &errctx));
395                 lls_free_parse_result(cf_lpr, CMD_PTR);
396                 if (ret < 0)
397                         goto free_cf;
398                 lls_free_parse_result(lpr, CMD_PTR);
399                 lpr = merged_lpr;
400                 loglevel = OPT_UINT32_VAL(LOGLEVEL);
401         }
402         init_mixers();
403         m = get_mixer_or_die();
404         ret = m->open(OPT_STRING_VAL(MIXER_DEVICE), &h);
405         if (ret < 0)
406                 goto free_cf;
407         ret = set_channel(m, h, OPT_STRING_VAL(MIXER_CHANNEL));
408         if (ret == -E_BAD_CHANNEL) {
409                 char *channels = m->get_channels(h);
410                 printf("Available channels: %s\n", channels);
411                 free(channels);
412         }
413         if (ret < 0)
414                 goto close_mixer;
415         if (strcmp(OPT_STRING_VAL(MODE), "fade") == 0)
416                 ret = fade(m, h, OPT_UINT32_VAL(FADE_VOL),
417                         OPT_UINT32_VAL(FADE_TIME));
418         else if (strcmp(OPT_STRING_VAL(MODE), "snooze") == 0)
419                 ret = snooze(m, h);
420         else if (strcmp(OPT_STRING_VAL(MODE), "set") == 0)
421                 ret = set_val(m, h);
422         else if (strcmp(OPT_STRING_VAL(MODE), "sleep") == 0)
423                 ret = sweet_dreams(m, h);
424         else {
425                 PARA_ERROR_LOG("invalid mode: %s\n", OPT_STRING_VAL(MODE));
426                 ret = -ERRNO_TO_PARA_ERROR(EINVAL);
427         }
428 close_mixer:
429         m->close(&h);
430 free_cf:
431         free(cf);
432         lls_free_parse_result(lpr, CMD_PTR);
433         if (ret >= 0)
434                 return EXIT_SUCCESS;
435 fail:
436         if (errctx)
437                 PARA_ERROR_LOG("%s\n", errctx);
438         free(errctx);
439         PARA_EMERG_LOG("%s\n", para_strerror(-ret));
440         return EXIT_FAILURE;
441 }