2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * Updated for Mac OSX CoreAudio
9 * by Josh Roberson <josh@asteriasgi.com>
11 * See http://www.asterisk.org for more information about
12 * the Asterisk project. Please do not directly contact
13 * any of the maintainers of this project for assistance;
14 * the project provides a web site, mailing lists and IRC
15 * channels for your use.
17 * This program is free software, distributed under the terms of
18 * the GNU General Public License Version 2. See the LICENSE file
19 * at the top of the source tree.
26 * \author Mark Spencer <markster@digium.com>
28 * Updated for Mac OSX CoreAudio
29 * \arg Josh Roberson <josh@asteriasgi.com>
31 * \note Specially written for Malcolm Davenport, but I think I'll use it too
32 * Connects to the Asterisk Manager Interface, AMI, and listens for events
33 * on certain devices. If a phone call is connected to one of the devices (phones)
34 * the local sound is muted to a lower volume during the call.
39 <support_level>extended</support_level>
42 #include "asterisk/autoconfig.h"
45 #include <CoreAudio/AudioHardware.h>
46 #include <sys/types.h>
49 #elif defined(__linux__) || defined(__FreeBSD__) || defined(__GLIBC__)
50 #include <sys/soundcard.h>
59 #include <sys/socket.h>
60 #include <sys/ioctl.h>
61 #include <netinet/in.h>
62 #include <arpa/inet.h>
64 #define ast_strlen_zero(a) (!(*(a)))
66 static char *config = "/etc/asterisk/muted.conf";
68 static char host[256] = "";
69 static char user[256] = "";
70 static char pass[256] = "";
71 static int smoothfade = 0;
72 static int mutelevel = 20;
74 static int needfork = 1;
76 static int stepsize = 3;
78 static int mixchan = SOUND_MIXER_VOLUME;
83 struct subchannel *next;
86 static struct channel {
90 struct subchannel *subs;
93 static void add_channel(char *tech, char *location)
96 chan = malloc(sizeof(struct channel));
98 memset(chan, 0, sizeof(struct channel));
99 if (!(chan->tech = strdup(tech))) {
103 if (!(chan->location = strdup(location))) {
108 chan->next = channels;
114 static int load_config(void)
122 f = fopen(config, "r");
124 fprintf(stderr, "Unable to open config file '%s': %s\n", config, strerror(errno));
128 if (!fgets(buf, sizeof(buf), f)) {
133 val = strchr(buf, '#');
134 if (val) *val = '\0';
135 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
136 buf[strlen(buf) - 1] = '\0';
148 while(*val && (*val < 33)) val++;
150 if (!strcasecmp(buf, "host")) {
151 if (val && strlen(val))
152 strncpy(host, val, sizeof(host) - 1);
154 fprintf(stderr, "host needs an argument (the host) at line %d\n", lineno);
155 } else if (!strcasecmp(buf, "user")) {
156 if (val && strlen(val))
157 strncpy(user, val, sizeof(user) - 1);
159 fprintf(stderr, "user needs an argument (the user) at line %d\n", lineno);
160 } else if (!strcasecmp(buf, "pass")) {
161 if (val && strlen(val))
162 strncpy(pass, val, sizeof(pass) - 1);
164 fprintf(stderr, "pass needs an argument (the password) at line %d\n", lineno);
165 } else if (!strcasecmp(buf, "smoothfade")) {
167 } else if (!strcasecmp(buf, "mutelevel")) {
168 if (val && (sscanf(val, "%3d", &x) == 1) && (x > -1) && (x < 101)) {
171 fprintf(stderr, "mutelevel must be a number from 0 (most muted) to 100 (no mute) at line %d\n", lineno);
172 } else if (!strcasecmp(buf, "channel")) {
173 if (val && strlen(val)) {
174 val2 = strchr(val, '/');
178 add_channel(val, val2);
180 fprintf(stderr, "channel needs to be of the format Tech/Location at line %d\n", lineno);
182 fprintf(stderr, "channel needs an argument (the channel) at line %d\n", lineno);
184 fprintf(stderr, "ignoring unknown keyword '%s'\n", buf);
190 fprintf(stderr, "no 'host' specification in config file\n");
191 else if (!strlen(user))
192 fprintf(stderr, "no 'user' specification in config file\n");
194 fprintf(stderr, "no 'channel' specifications in config file\n");
204 static int open_mixer(void)
206 mixfd = open("/dev/mixer", O_RDWR);
208 fprintf(stderr, "Unable to open /dev/mixer: %s\n", strerror(errno));
213 #endif /* !__Darwin */
215 /*! Connect to the asterisk manager interface */
216 static int connect_asterisk(void)
222 struct sockaddr_in sin;
224 ports = strchr(host, ':');
228 if ((sscanf(ports, "%5d", &port) != 1) || (port < 1) || (port > 65535)) {
229 fprintf(stderr, "'%s' is not a valid port number in the hostname\n", ports);
233 hp = gethostbyname(host);
235 fprintf(stderr, "Can't find host '%s'\n", host);
238 sock = socket(AF_INET, SOCK_STREAM, 0);
240 fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
243 sin.sin_family = AF_INET;
244 sin.sin_port = htons(port);
245 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
246 if (connect(sock, (struct sockaddr *)&sin, sizeof(sin))) {
247 fprintf(stderr, "Failed to connect to '%s' port '%d': %s\n", host, port, strerror(errno));
251 astf = fdopen(sock, "r+");
253 fprintf(stderr, "fdopen failed: %s\n", strerror(errno));
260 static char *get_line(void)
262 static char buf[1024];
263 if (fgets(buf, sizeof(buf), astf)) {
264 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
265 buf[strlen(buf) - 1] = '\0';
271 /*! Login to the asterisk manager interface */
272 static int login_asterisk(void)
276 if (!(welcome = get_line())) {
277 fprintf(stderr, "disconnected (1)\n");
283 "Secret: %s\r\n\r\n", user, pass);
284 if (!(welcome = get_line())) {
285 fprintf(stderr, "disconnected (2)\n");
288 if (strcasecmp(welcome, "Response: Success")) {
289 fprintf(stderr, "login failed ('%s')\n", welcome);
292 /* Eat the rest of the event */
293 while((resp = get_line()) && strlen(resp));
295 fprintf(stderr, "disconnected (3)\n");
299 "Action: Status\r\n\r\n");
300 if (!(welcome = get_line())) {
301 fprintf(stderr, "disconnected (4)\n");
304 if (strcasecmp(welcome, "Response: Success")) {
305 fprintf(stderr, "status failed ('%s')\n", welcome);
308 /* Eat the rest of the event */
309 while((resp = get_line()) && strlen(resp));
311 fprintf(stderr, "disconnected (5)\n");
317 static struct channel *find_channel(char *channel)
321 struct channel *chan;
322 strncpy(tmp, channel, sizeof(tmp) - 1);
323 s = strchr(tmp, '/');
332 printf("Searching for '%s' tech, '%s' location\n", tmp, s);
335 if (!strcasecmp(chan->tech, tmp) && !strcasecmp(chan->location, s)) {
337 printf("Found '%s'/'%s'\n", chan->tech, chan->location);
348 static int getvol(void)
352 if (ioctl(mixfd, MIXER_READ(mixchan), &vol)) {
354 static float getvol(void)
356 float volumeL, volumeR, vol;
358 AudioDeviceID device;
361 AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
362 AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
363 AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
365 size = sizeof(device);
366 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
367 size = sizeof(channels);
369 err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
373 VolumeAddr.mElement = channels[0];
374 err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeL);
377 VolumeAddr.mElement = channels[1];
378 err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeR);
381 vol = (volumeL < volumeR) ? volumeR : volumeL;
384 fprintf(stderr, "Unable to read mixer volume: %s\n", strerror(errno));
391 static int setvol(int vol)
393 static int setvol(float vol)
397 if (ioctl(mixfd, MIXER_WRITE(mixchan), &vol)) {
402 AudioDeviceID device;
405 AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
406 AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
407 AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
409 size = sizeof(device);
410 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
411 size = sizeof(channels);
412 err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
415 VolumeAddr.mElement = channels[0];
416 err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeL);
419 VolumeAddr.mElement = channels[1];
420 err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeR);
425 fprintf(stderr, "Unable to write mixer volume: %s\n", strerror(errno));
433 static int oldvol = 0;
434 static int mutevol = 0;
436 static float oldvol = 0;
437 static float mutevol = 0;
441 static int mutedlevel(int orig, int level)
445 l = (float)(level) * (float)(l) / 100.0;
446 r = (float)(level) * (float)(r) / 100.0;
450 static float mutedlevel(float orig, float level)
453 master = level * master / 100.0;
459 static void mute(void)
480 for (x=start;x>=mutelevel;x-=stepsize) {
481 mutevol = mutedlevel(vol, x);
486 mutevol = mutedlevel(vol, mutelevel);
490 printf("Mute from '%f' to '%f'!\n", oldvol, mutevol);
492 printf("Mute from '%04x' to '%04x'!\n", oldvol, mutevol);
497 static void unmute(void)
511 printf("Unmute from '%f' (should be '%f') to '%f'!\n", vol, mutevol, oldvol);
513 if (vol == mutevol) {
515 printf("Unmute from '%04x' (should be '%04x') to '%04x'!\n", vol, mutevol, oldvol);
516 if ((int)vol == mutevol) {
526 for (x=start;x<100;x+=stepsize) {
527 mutevol = mutedlevel(oldvol, x);
534 printf("Whoops, it's already been changed!\n");
538 static void check_mute(void)
541 struct channel *chan;
550 if (offhook && !muted)
552 else if (!offhook && muted)
556 static void delete_sub(struct channel *chan, char *name)
558 struct subchannel *sub, *prev;
562 if (!strcasecmp(sub->name, name)) {
564 prev->next = sub->next;
566 chan->subs = sub->next;
576 static void append_sub(struct channel *chan, char *name)
578 struct subchannel *sub;
581 if (!strcasecmp(sub->name, name))
585 sub = malloc(sizeof(struct subchannel));
587 memset(sub, 0, sizeof(struct subchannel));
588 if (!(sub->name = strdup(name))) {
592 sub->next = chan->subs;
597 static void hangup_chan(char *channel)
599 struct channel *chan;
601 printf("Hangup '%s'\n", channel);
602 chan = find_channel(channel);
604 delete_sub(chan, channel);
608 static void offhook_chan(char *channel)
610 struct channel *chan;
612 printf("Offhook '%s'\n", channel);
613 chan = find_channel(channel);
615 append_sub(chan, channel);
619 static int wait_event(void)
623 char channel[120]="";
624 char oldname[120]="";
625 char newname[120]="";
629 fprintf(stderr, "disconnected (6)\n");
632 if (!strncasecmp(resp, "Event: ", strlen("Event: "))) {
633 strncpy(event, resp + strlen("Event: "), sizeof(event) - 1);
634 /* Consume the rest of the non-event */
635 while((resp = get_line()) && strlen(resp)) {
636 if (!strncasecmp(resp, "Channel: ", strlen("Channel: ")))
637 strncpy(channel, resp + strlen("Channel: "), sizeof(channel) - 1);
638 if (!strncasecmp(resp, "Newname: ", strlen("Newname: ")))
639 strncpy(newname, resp + strlen("Newname: "), sizeof(newname) - 1);
640 if (!strncasecmp(resp, "Oldname: ", strlen("Oldname: ")))
641 strncpy(oldname, resp + strlen("Oldname: "), sizeof(oldname) - 1);
643 if (strlen(channel)) {
644 if (!strcasecmp(event, "Hangup"))
645 hangup_chan(channel);
647 offhook_chan(channel);
649 if (strlen(newname) && strlen(oldname)) {
650 if (!strcasecmp(event, "Rename")) {
651 hangup_chan(oldname);
652 offhook_chan(newname);
656 /* Consume the rest of the non-event */
657 while((resp = get_line()) && strlen(resp));
660 fprintf(stderr, "disconnected (7)\n");
666 static void usage(void)
668 printf("Usage: muted [-f] [-d]\n"
669 " -f : Do not fork\n"
670 " -d : Debug (implies -f)\n");
673 int main(int argc, char *argv[])
676 while((x = getopt(argc, argv, "fhd")) > 0) {
698 if (connect_asterisk()) {
704 if (login_asterisk()) {
711 #ifdef HAVE_WORKING_FORK
713 #ifndef HAVE_SBIN_LAUNCHD
714 if (daemon(0,0) < 0) {
715 fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
719 const char *found = NULL, *paths[] = {
720 "/Library/LaunchAgents/org.asterisk.muted.plist",
721 "/Library/LaunchDaemons/org.asterisk.muted.plist",
722 "contrib/init.d/org.asterisk.muted.plist",
723 "<path-to-asterisk-source>/contrib/init.d/org.asterisk.muted.plist" };
726 struct passwd *pwd = getpwuid(getuid());
729 snprintf(userpath, sizeof(userpath), "%s%s", pwd->pw_dir, paths[0]);
730 if (!stat(userpath, &unused)) {
735 for (i = 0; i < 3; i++) {
736 if (!stat(paths[i], &unused)) {
743 fprintf(stderr, "Mac OS X detected. Use 'launchctl load -w %s' to launch.\n", found ? found : paths[3]);
745 #endif /* !defined(HAVE_SBIN_LAUNCHD */
751 while(connect_asterisk()) {
754 if (login_asterisk()) {