Add muted for malcolm!
[asterisk/asterisk.git] / muted.c
1 /*
2  * Mute Daemon
3  *
4  * Specially written for Malcolm Davenport, but I think I'll use it too
5  *
6  * Copyright (C) 2004, Digium Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * Distributed under the terms of the GNU General Public License version 2.0 
11  *
12  */
13 #include <linux/soundcard.h>
14 #include <stdio.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <string.h>
20 #include <netdb.h>
21 #include <sys/socket.h>
22 #include <sys/ioctl.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25
26 static char *config = "/etc/muted.conf";
27
28 static char host[256];
29 static char user[256];
30 static char pass[256];
31 static int smoothfade = 0;
32 static int mutelevel = 20;
33 static int muted = 0;
34 static int needfork = 1;
35 static int debug = 0;
36 static int stepsize = 3;
37 static int mixchan = SOUND_MIXER_VOLUME;
38
39 static struct channel {
40         char *tech;
41         char *location;
42         struct channel *next;
43         int offhook;
44 } *channels;
45
46 static void add_channel(char *tech, char *location)
47 {
48         struct channel *chan;
49         chan = malloc(sizeof(struct channel));
50         if (chan) {
51                 memset(chan, 0, sizeof(struct channel));
52                 chan->tech = strdup(tech);
53                 chan->location = strdup(location);
54                 chan->next = channels;
55                 channels = chan;
56         }
57         
58 }
59
60 static int load_config(void)
61 {
62         FILE *f;
63         char buf[1024];
64         char *val;
65         char *val2;
66         int lineno=0;
67         int x;
68         f = fopen(config, "r");
69         if (!f) {
70                 fprintf(stderr, "Unable to open config file '%s': %s\n", config, strerror(errno));
71                 return -1;
72         }
73         while(!feof(f)) {
74                 fgets(buf, sizeof(buf), f);
75                 if (!feof(f)) {
76                         lineno++;
77                         val = strchr(buf, '#');
78                         if (val) *val = '\0';
79                         while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
80                                 buf[strlen(buf) - 1] = '\0';
81                         if (!strlen(buf))
82                                 continue;
83                         val = buf;
84                         while(*val) {
85                                 if (*val < 33)
86                                         break;
87                                 val++;
88                         }
89                         if (*val) {
90                                 *val = '\0';
91                                 val++;
92                                 while(*val && (*val < 33)) val++;
93                         }
94                         if (!strcasecmp(buf, "host")) {
95                                 if (val && strlen(val))
96                                         strncpy(host, val, sizeof(host));
97                                 else
98                                         fprintf(stderr, "host needs an argument (the host) at line %d\n", lineno);
99                         } else if (!strcasecmp(buf, "user")) {
100                                 if (val && strlen(val))
101                                         strncpy(user, val, sizeof(user));
102                                 else
103                                         fprintf(stderr, "user needs an argument (the user) at line %d\n", lineno);
104                         } else if (!strcasecmp(buf, "pass")) {
105                                 if (val && strlen(val))
106                                         strncpy(pass, val, sizeof(pass));
107                                 else
108                                         fprintf(stderr, "pass needs an argument (the password) at line %d\n", lineno);
109                         } else if (!strcasecmp(buf, "smoothfade")) {
110                                 smoothfade = 1;
111                         } else if (!strcasecmp(buf, "mutelevel")) {
112                                 if (val && (sscanf(val, "%d", &x) == 1) && (x > -1) && (x < 101)) {
113                                         mutelevel = x;
114                                 } else 
115                                         fprintf(stderr, "mutelevel must be a number from 0 (most muted) to 100 (no mute) at line %d\n", lineno);
116                         } else if (!strcasecmp(buf, "channel")) {
117                                 if (val && strlen(val)) {
118                                         val2 = strchr(val, '/');
119                                         if (val2) {
120                                                 *val2 = '\0';
121                                                 val2++;
122                                                 add_channel(val, val2);
123                                         } else
124                                                 fprintf(stderr, "channel needs to be of the format Tech/Location at line %d\n", lineno);
125                                 } else
126                                         fprintf(stderr, "channel needs an argument (the channel) at line %d\n", lineno);
127                         } else {
128                                 fprintf(stderr, "ignoring unknown keyword '%s'\n", buf);
129                         }
130                 }
131         }
132         fclose(f);
133         if (!strlen(host))
134                 fprintf(stderr, "no 'host' specification in config file\n");
135         else if (!strlen(user))
136                 fprintf(stderr, "no 'user' specification in config file\n");
137         else if (!channels) 
138                 fprintf(stderr, "no 'channel' specifications in config file\n");
139         else
140                 return 0;
141         return -1;
142 }
143
144 static FILE *astf;
145
146 static int mixfd;
147
148 static int open_mixer(void)
149 {
150         mixfd = open("/dev/mixer", O_RDWR);
151         if (mixfd < 0) {
152                 fprintf(stderr, "Unable to open /dev/mixer: %s\n", strerror(errno));
153                 return -1;
154         }
155         return 0;
156 }
157
158 static int connect_asterisk(void)
159 {
160         int sock;
161         struct hostent *hp;
162         char *ports;
163         int port = 5038;
164         struct sockaddr_in sin;
165         ports = strchr(host, ':');
166         if (ports) {
167                 *ports = '\0';
168                 ports++;
169                 if ((sscanf(ports, "%d", &port) != 1) || (port < 1) || (port > 65535)) {
170                         fprintf(stderr, "'%s' is not a valid port number in the hostname\n", ports);
171                         return -1;
172                 }
173         }
174         hp = gethostbyname(host);
175         if (!hp) {
176                 fprintf(stderr, "Can't find host '%s'\n", host);
177                 return -1;
178         }
179         sock = socket(AF_INET, SOCK_STREAM, 0);
180         if (sock < 0) {
181                 fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
182                 return -1;
183         }
184         sin.sin_family = AF_INET;
185         sin.sin_port = htons(port);
186         memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
187         if (connect(sock, &sin, sizeof(sin))) {
188                 fprintf(stderr, "Failed to connect to '%s' port '%d': %s\n", host, port, strerror(errno));
189                 close(sock);
190                 return -1;
191         }
192         astf = fdopen(sock, "r+");
193         if (!astf) {
194                 fprintf(stderr, "fdopen failed: %s\n", strerror(errno));
195                 close(sock);
196                 return -1;
197         }
198         return 0;
199 }
200
201 static char *get_line(void)
202 {
203         static char buf[1024];
204         if (fgets(buf, sizeof(buf), astf)) {
205                 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
206                         buf[strlen(buf) - 1] = '\0';
207                 return buf;
208         } else
209                 return NULL;
210 }
211
212 static int login_asterisk(void)
213 {
214         char *welcome;
215         char *resp;
216         if (!(welcome = get_line())) {
217                 fprintf(stderr, "disconnected (1)\n");
218                 return -1;
219         }
220         fprintf(astf, 
221                 "Action: Login\r\n"
222                 "Username: %s\r\n"
223                 "Secret: %s\r\n\r\n", user, pass);
224         if (!(welcome = get_line())) {
225                 fprintf(stderr, "disconnected (2)\n");
226                 return -1;
227         }
228         if (strcasecmp(welcome, "Response: Success")) {
229                 fprintf(stderr, "login failed ('%s')\n", welcome);
230                 return -1;
231         }
232         /* Eat the rest of the event */
233         while((resp = get_line()) && strlen(resp));
234         if (!resp) {
235                 fprintf(stderr, "disconnected (3)\n");
236                 return -1;
237         }
238         fprintf(astf, 
239                 "Action: Status\r\n\r\n");
240         if (!(welcome = get_line())) {
241                 fprintf(stderr, "disconnected (4)\n");
242                 return -1;
243         }
244         if (strcasecmp(welcome, "Response: Success")) {
245                 fprintf(stderr, "status failed ('%s')\n", welcome);
246                 return -1;
247         }
248         /* Eat the rest of the event */
249         while((resp = get_line()) && strlen(resp));
250         if (!resp) {
251                 fprintf(stderr, "disconnected (5)\n");
252                 return -1;
253         }
254         return 0;
255 }
256
257 static struct channel *find_channel(char *channel)
258 {
259         char tmp[256] = "";
260         char *s, *t;
261         struct channel *chan;
262         strncpy(tmp, channel, sizeof(tmp));
263         s = strchr(tmp, '/');
264         if (s) {
265                 *s = '\0';
266                 s++;
267                 t = strrchr(s, '-');
268                 if (t) {
269                         *t = '\0';
270                 }
271                 if (debug)
272                         printf("Searching for '%s' tech, '%s' location\n", tmp, s);
273                 chan = channels;
274                 while(chan) {
275                         if (!strcasecmp(chan->tech, tmp) && !strcasecmp(chan->location, s)) {
276                                 if (debug)
277                                         printf("Found '%s'/'%s'\n", chan->tech, chan->location);
278                                 break;
279                         }
280                         chan = chan->next;
281                 }
282         } else
283                 chan = NULL;
284         return chan;
285 }
286
287 static int getvol(void)
288 {
289         int vol;
290         if (ioctl(mixfd, MIXER_READ(mixchan), &vol)) {
291                 fprintf(stderr, "Unable to read mixer volume: %s\n", strerror(errno));
292                 return -1;
293         }
294         return vol;
295 }
296
297 static int setvol(int vol)
298 {
299         if (ioctl(mixfd, MIXER_WRITE(mixchan), &vol)) {
300                 fprintf(stderr, "Unable to write mixer volume: %s\n", strerror(errno));
301                 return -1;
302         }
303         return 0;
304 }
305
306 static int oldvol = 0;
307 static int mutevol = 0;
308
309 static int mutedlevel(int orig, int mutelevel)
310 {
311         int l = orig >> 8;
312         int r = orig & 0xff;
313         l = (float)(mutelevel) * (float)(l) / 100.0;
314         r = (float)(mutelevel) * (float)(r) / 100.0;
315         return (l << 8) | r;
316 }
317
318 static void mute(void)
319 {
320         int vol;
321         int start;
322         int x;
323         vol = getvol();
324         oldvol = vol;
325         if (smoothfade) 
326                 start = 100;
327         else
328                 start = mutelevel;
329         for (x=start;x>=mutelevel;x-=stepsize) {
330                 mutevol = mutedlevel(vol, x);
331                 setvol(mutevol);
332                 /* Wait 0.01 sec */
333                 usleep(10000);
334         }
335         mutevol = mutedlevel(vol, mutelevel);
336         setvol(mutevol);
337         if (debug)
338                 printf("Mute from '%04x' to '%04x'!\n", oldvol, mutevol);
339         muted = 1;
340 }
341
342 static void unmute(void)
343 {
344         int vol;
345         int start;
346         int x;
347         vol = getvol();
348         if (debug)
349                 printf("Unmute from '%04x' (should be '%04x') to '%04x'!\n", vol, mutevol, oldvol);
350         if (vol == mutevol) {
351                 if (smoothfade)
352                         start = mutelevel;
353                 else
354                         start = 100;
355                 for (x=start;x<100;x+=stepsize) {
356                         mutevol = mutedlevel(oldvol, x);
357                         setvol(mutevol);
358                         /* Wait 0.01 sec */
359                         usleep(10000);
360                 }
361                 setvol(oldvol);
362         } else
363                 printf("Whoops, it's already been changed!\n");
364         muted = 0;
365 }
366
367 static void check_mute(void)
368 {
369         int offhook = 0;
370         struct channel *chan;
371         chan = channels;
372         while(chan) {
373                 if (chan->offhook) {
374                         offhook++;
375                         break;
376                 }
377                 chan = chan->next;
378         }
379         if (offhook && !muted)
380                 mute();
381         else if (!offhook && muted)
382                 unmute();
383 }
384
385 static void hangup_chan(char *channel)
386 {
387         struct channel *chan;
388         chan = find_channel(channel);
389         if (chan)
390                 chan->offhook = 0;
391         check_mute();
392 }
393
394 static void offhook_chan(char *channel)
395 {
396         struct channel *chan;
397         chan = find_channel(channel);
398         if (chan)
399                 chan->offhook = 1;
400         check_mute();
401 }
402
403 static int wait_event(void)
404 {
405         char *resp;
406         char event[80]="";
407         char channel[80]="";
408         resp = get_line();
409         if (!resp) {
410                 fprintf(stderr, "disconnected (6)\n");
411                 return -1;
412         }
413         if (!strncasecmp(resp, "Event: ", strlen("Event: "))) {
414                 strncpy(event, resp + strlen("Event: "), sizeof(event));
415                 /* Consume the rest of the non-event */
416                 while((resp = get_line()) && strlen(resp)) {
417                         if (!strncasecmp(resp, "Channel: ", strlen("Channel: ")))
418                                 strncpy(channel, resp + strlen("Channel: "), sizeof(channel));
419                 }
420                 if (strlen(channel)) {
421                         if (!strcasecmp(event, "Hangup")) 
422                                 hangup_chan(channel);
423                         else
424                                 offhook_chan(channel);
425                 }
426         } else {
427                 /* Consume the rest of the non-event */
428                 while((resp = get_line()) && strlen(resp));
429         }
430         if (!resp) {
431                 fprintf(stderr, "disconnected (7)\n");
432                 return -1;
433         }
434         return 0;
435 }
436
437 static void usage(void)
438 {
439         printf("Usage: muted [-f] [-d]\n"
440                "        -f : Do not fork\n"
441                    "        -d : Debug (implies -f)\n");
442 }
443
444 int main(int argc, char *argv[])
445 {
446         int x;
447         while((x = getopt(argc, argv, "fhd")) > 0) {
448                 switch(x) {
449                 case 'd':
450                         debug = 1;
451                         needfork = 0;
452                         break;
453                 case 'f':
454                         needfork = 0;
455                         break;
456                 case 'h':
457                         /* Fall through */
458                 default:
459                         usage();
460                         exit(1);
461                 }
462         }
463         if (load_config())
464                 exit(1);
465         if (open_mixer())
466                 exit(1);
467         if (connect_asterisk()) {
468                 close(mixfd);
469                 exit(1);
470         }
471         if (login_asterisk()) {
472                 close(mixfd);
473                 fclose(astf);
474                 exit(1);
475         }
476         if (needfork)
477                 daemon(0,0);
478         for(;;) {
479                 if (wait_event())
480                         exit(1);
481         }
482         exit(0);
483 }