fade: Introduce --mixer-api to choose between ALSA and OSS.
[paraslash.git] / fade.c
1 /*
2  * Copyright (C) 1998-2012 Andre Noll <maan@systemlinux.org>
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
11 #include "fade.cmdline.h"
12 #include "para.h"
13 #include "fd.h"
14 #include "string.h"
15 #include "mix.h"
16 #include "error.h"
17 #include "version.h"
18
19 INIT_FADE_ERRLISTS;
20 static struct fade_args_info conf;
21
22 enum mixer_id {MIXER_ENUM};
23 static char *mixer_name[] = {MIXER_NAMES};
24 DECLARE_MIXER_INITS;
25 static struct mixer supported_mixer[] = {MIXER_ARRAY};
26 #define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++)
27
28 __printf_2_3 void date_log(__a_unused int ll, const char *fmt, ...)
29 {
30         va_list argp;
31         time_t t1;
32         struct tm *tm;
33
34         time(&t1);
35         tm = localtime(&t1);
36         printf("%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
37         va_start(argp, fmt);
38         vprintf(fmt, argp);
39         va_end(argp);
40 }
41 __printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
42
43 /* Fade to new volume in fade_time seconds. */
44 static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_time)
45 {
46         int vol, diff, incr, ret;
47         unsigned secs;
48         struct timespec ts;
49         unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
50
51         if (fade_time <= 0)
52                 return 1;
53         secs = fade_time;
54         PARA_NOTICE_LOG("fading to %d in %d seconds\n", new_vol, secs);
55         ret = m->get(h);
56         if (ret < 0)
57                 goto out;
58         vol = ret;
59         diff = new_vol - vol;
60         if (!diff) {
61                 sleep(secs);
62                 goto out;
63         }
64         incr = diff > 0? 1: -1;
65         diff = diff > 0? diff: -diff;
66         tmp = secs * 1000 / diff;
67         tmp2 = tmp % 1000;
68         while ((new_vol - vol) * incr > 0) {
69                 ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/
70                 ts.tv_sec = tmp / 1000; /* really nec ?*/
71                 //printf("ts.tv_sec: %i\n", ts.tv_nsec);
72                 vol += incr;
73                 ret = m->set(h, vol);
74                 if (ret < 0)
75                         goto out;
76                 //printf("vol = %i\n", vol);
77                 nanosleep(&ts, NULL);
78         }
79 out:
80         return ret;
81 }
82
83 static void client_cmd(const char *cmd)
84 {
85         int ret, status, fds[3] = {0, 0, 0};
86         pid_t pid;
87         char *cmdline = make_message(BINDIR "/para_client %s", cmd);
88
89         PARA_INFO_LOG("%s\n", cmdline);
90         ret = para_exec_cmdline_pid(&pid, cmdline, fds);
91         free(cmdline);
92         if (ret < 0) {
93                 PARA_ERROR_LOG("%s\n", para_strerror(-ret));
94                 goto fail;
95         }
96         do
97                 pid = waitpid(pid, &status, 0);
98         while (pid == -1 && errno == EINTR);
99         if (pid < 0) {
100                 PARA_ERROR_LOG("%s\n", strerror(errno));
101                 goto fail;
102         }
103         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
104                 goto fail;
105         return;
106 fail:
107         PARA_EMERG_LOG("command \"%s\" failed\n", cmd);
108         exit(EXIT_FAILURE);
109 }
110
111 static void change_afs_mode_and_play(char *afs_mode)
112 {
113         char *cmd;
114
115         client_cmd("stop");
116         if (!afs_mode)
117                 return;
118         cmd = make_message("select %s", afs_mode);
119         client_cmd(cmd);
120         free(cmd);
121         client_cmd("play");
122 }
123
124 static int sweet_dreams(struct mixer *m, struct mixer_handle *h)
125 {
126         time_t t1, wake_time_epoch;
127         unsigned int delay;
128         struct tm *tm;
129         int ret, min = conf.wake_min_arg;
130         char *fo_mood = conf.fo_mood_arg;
131         char *fi_mood = conf.fi_mood_arg;
132         char *sleep_mood = conf.sleep_mood_arg;
133         int fit = conf.fi_time_arg;
134         int fot = conf.fo_time_arg;
135         int fiv = conf.fi_vol_arg;
136         int fov = conf.fo_vol_arg;
137         int iv = conf.ivol_arg;
138
139         /* calculate wake time */
140         time(&t1);
141         if (conf.wake_hour_given) {
142                 int hour = conf.wake_hour_arg;
143                 tm = localtime(&t1);
144                 if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
145                         t1 += 86400; /* wake time is tomorrow */
146                         tm = localtime(&t1);
147                 }
148                 tm->tm_hour = hour;
149                 tm->tm_min = min;
150                 tm->tm_sec = 0;
151         } else {
152                 t1 += 9 * 60 * 60; /* nine hours from now */
153                 PARA_INFO_LOG("default wake time: %lu\n", (long unsigned)t1);
154                 tm = localtime(&t1);
155         }
156         wake_time_epoch = mktime(tm);
157         PARA_INFO_LOG("waketime: %s", asctime(tm));
158         client_cmd("stop");
159         sleep(1);
160         if (fot) {
161                 PARA_INFO_LOG("initial volume: %d\n", iv);
162                 ret = m->set(h, iv);
163                 if (ret < 0)
164                         return ret;
165                 change_afs_mode_and_play(fo_mood);
166                 ret = fade(m, h, fov, fot);
167                 if (ret < 0)
168                         return ret;
169         } else {
170                 ret = m->set(h, fov);
171                 if (ret < 0)
172                         return ret;
173         }
174         if (conf.sleep_mood_given)
175                 change_afs_mode_and_play(sleep_mood);
176         else
177                 client_cmd("stop");
178         if (!fit)
179                 return 1;
180         for (;;) {
181                 time(&t1);
182                 if (wake_time_epoch <= t1 + fit)
183                         break;
184                 delay = wake_time_epoch - t1 - fit;
185                 PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
186                         delay, delay / 3600,
187                         (delay % 3600) / 60);
188                 sleep(delay);
189         }
190         change_afs_mode_and_play(fi_mood);
191         ret = fade(m, h, fiv, fit);
192         PARA_INFO_LOG("fade complete, returning\n");
193         return ret;
194 }
195
196 static int snooze(struct mixer *m, struct mixer_handle *h)
197 {
198         int ret, val;
199
200         if (conf.so_time_arg <= 0)
201                 return 1;
202         ret = m->get(h);
203         if (ret < 0)
204                 return ret;
205         val = ret;
206         if (val < conf.so_vol_arg)
207                 ret = m->set(h, conf.so_vol_arg);
208         else
209                 ret = fade(m, h, conf.so_vol_arg, conf.so_time_arg);
210         if (ret < 0)
211                 return ret;
212         client_cmd("pause");
213         PARA_NOTICE_LOG("%d seconds snooze time...\n", conf.snooze_time_arg);
214         sleep(conf.snooze_time_arg);
215         client_cmd("play");
216         return fade(m, h, conf.si_vol_arg, conf.si_time_arg);
217 }
218
219 static int configfile_exists(void)
220 {
221         static char *config_file;
222
223         if (!conf.config_file_given) {
224                 char *home = para_homedir();
225                 free(config_file);
226                 config_file = make_message("%s/.paraslash/fade.conf", home);
227                 free(home);
228                 conf.config_file_arg = config_file;
229         }
230         return file_exists(conf.config_file_arg);
231 }
232
233 static void init_mixers(void)
234 {
235         int i;
236
237         FOR_EACH_MIXER(i) {
238                 struct mixer *m = &supported_mixer[i];
239                 PARA_DEBUG_LOG("initializing mixer API #%d (%s)\n",
240                         i, mixer_name[i]);
241                 m->init(m);
242         }
243 }
244
245 static int set_channel(struct mixer *m, struct mixer_handle *h)
246 {
247         char *channels;
248         int ret;
249
250         ret = m->set_channel(h, conf.mixer_channel_arg);
251         if (ret >= 0) {
252                 PARA_NOTICE_LOG("using %s mixer channel\n",
253                         conf.mixer_channel_arg?  conf.mixer_channel_arg
254                                 : "default");
255                 return ret;
256         }
257         channels = m->get_channels(h);
258         printf("Available channels: %s\n", channels);
259         free(channels);
260         return ret;
261 }
262
263 static struct mixer *get_mixer_or_die(void)
264 {
265         int i;
266
267         if (!conf.mixer_api_given)
268                 i = DEFAULT_MIXER;
269         else
270                 FOR_EACH_MIXER(i)
271                         if (!strcmp(mixer_name[i], conf.mixer_api_arg))
272                                 break;
273         if (i < NUM_SUPPORTED_MIXERS) {
274                 PARA_NOTICE_LOG("using %s mixer API\n", mixer_name[i]);
275                 return supported_mixer + i;
276         }
277         printf("available mixer APIs: ");
278         FOR_EACH_MIXER(i) {
279                 int d = (i == DEFAULT_MIXER);
280                 printf("%s%s%s ", d? "[" : "", mixer_name[i], d? "]" : "");
281         }
282         printf("\n");
283         exit(EXIT_FAILURE);
284 }
285
286 int main(int argc, char *argv[])
287 {
288         int ret;
289         struct mixer *m;
290         struct mixer_handle *h = NULL;
291
292         if (fade_cmdline_parser(argc, argv, &conf))
293                 exit(EXIT_FAILURE);
294         HANDLE_VERSION_FLAG("fade", conf);
295         ret = configfile_exists();
296         if (!ret && conf.config_file_given) {
297                 PARA_EMERG_LOG("can not read config file %s\n",
298                         conf.config_file_arg);
299                 exit(EXIT_FAILURE);
300         }
301         if (ret) {
302                 struct fade_cmdline_parser_params params = {
303                         .override = 0,
304                         .initialize = 0,
305                         .check_required = 0,
306                         .check_ambiguity = 0,
307                         .print_errors = 1
308                 };
309                 fade_cmdline_parser_config_file(conf.config_file_arg,
310                         &conf, &params);
311         }
312         init_mixers();
313         m = get_mixer_or_die();
314         ret = m->open(conf.mixer_device_arg, &h);
315         if (ret < 0)
316                 goto out;
317         ret = set_channel(m, h);
318         if (ret < 0)
319                 goto out;
320         switch (conf.mode_arg) {
321         case mode_arg_fade:
322                 ret = fade(m, h, conf.fade_vol_arg, conf.fade_time_arg);
323                 break;
324         case mode_arg_snooze:
325                 ret = snooze(m, h);
326                 break;
327         default: /* sleep mode */
328                 ret = sweet_dreams(m, h);
329                 break;
330         }
331 out:
332         m->close(&h);
333         if (ret < 0)
334                 PARA_EMERG_LOG("%s\n", para_strerror(-ret));
335         return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
336 }