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.
38 /*! \li \ref muted.c uses the configuration file \ref muted.conf
39 * \addtogroup configuration_file Configuration Files
43 * \page muted.conf muted.conf
44 * \verbinclude muted.conf.sample
48 <support_level>extended</support_level>
51 #include "asterisk/autoconfig.h"
54 #include <CoreAudio/AudioHardware.h>
55 #include <sys/types.h>
58 #elif defined(__linux__) || defined(__FreeBSD__) || defined(__GLIBC__)
59 #include <sys/soundcard.h>
68 #include <sys/socket.h>
69 #include <sys/ioctl.h>
70 #include <netinet/in.h>
71 #include <arpa/inet.h>
73 #define ast_strlen_zero(a) (!(*(a)))
75 static char *config = "/etc/asterisk/muted.conf";
77 static char host[256] = "";
78 static char user[256] = "";
79 static char pass[256] = "";
80 static int smoothfade = 0;
81 static int mutelevel = 20;
83 static int needfork = 1;
85 static int stepsize = 3;
87 static int mixchan = SOUND_MIXER_VOLUME;
92 struct subchannel *next;
95 static struct channel {
99 struct subchannel *subs;
102 static void add_channel(char *tech, char *location)
104 struct channel *chan;
105 chan = malloc(sizeof(struct channel));
107 memset(chan, 0, sizeof(struct channel));
108 if (!(chan->tech = strdup(tech))) {
112 if (!(chan->location = strdup(location))) {
117 chan->next = channels;
123 static int load_config(void)
131 f = fopen(config, "r");
133 fprintf(stderr, "Unable to open config file '%s': %s\n", config, strerror(errno));
137 if (!fgets(buf, sizeof(buf), f)) {
142 val = strchr(buf, '#');
143 if (val) *val = '\0';
144 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
145 buf[strlen(buf) - 1] = '\0';
157 while(*val && (*val < 33)) val++;
159 if (!strcasecmp(buf, "host")) {
160 if (val && strlen(val))
161 strncpy(host, val, sizeof(host) - 1);
163 fprintf(stderr, "host needs an argument (the host) at line %d\n", lineno);
164 } else if (!strcasecmp(buf, "user")) {
165 if (val && strlen(val))
166 strncpy(user, val, sizeof(user) - 1);
168 fprintf(stderr, "user needs an argument (the user) at line %d\n", lineno);
169 } else if (!strcasecmp(buf, "pass")) {
170 if (val && strlen(val))
171 strncpy(pass, val, sizeof(pass) - 1);
173 fprintf(stderr, "pass needs an argument (the password) at line %d\n", lineno);
174 } else if (!strcasecmp(buf, "smoothfade")) {
176 } else if (!strcasecmp(buf, "mutelevel")) {
177 if (val && (sscanf(val, "%3d", &x) == 1) && (x > -1) && (x < 101)) {
180 fprintf(stderr, "mutelevel must be a number from 0 (most muted) to 100 (no mute) at line %d\n", lineno);
181 } else if (!strcasecmp(buf, "channel")) {
182 if (val && strlen(val)) {
183 val2 = strchr(val, '/');
187 add_channel(val, val2);
189 fprintf(stderr, "channel needs to be of the format Tech/Location at line %d\n", lineno);
191 fprintf(stderr, "channel needs an argument (the channel) at line %d\n", lineno);
193 fprintf(stderr, "ignoring unknown keyword '%s'\n", buf);
199 fprintf(stderr, "no 'host' specification in config file\n");
200 else if (!strlen(user))
201 fprintf(stderr, "no 'user' specification in config file\n");
203 fprintf(stderr, "no 'channel' specifications in config file\n");
213 static int open_mixer(void)
215 mixfd = open("/dev/mixer", O_RDWR);
217 fprintf(stderr, "Unable to open /dev/mixer: %s\n", strerror(errno));
222 #endif /* !__Darwin */
224 /*! Connect to the asterisk manager interface */
225 static int connect_asterisk(void)
231 struct sockaddr_in sin;
233 ports = strchr(host, ':');
237 if ((sscanf(ports, "%5d", &port) != 1) || (port < 1) || (port > 65535)) {
238 fprintf(stderr, "'%s' is not a valid port number in the hostname\n", ports);
242 hp = gethostbyname(host);
244 fprintf(stderr, "Can't find host '%s'\n", host);
247 sock = socket(AF_INET, SOCK_STREAM, 0);
249 fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
252 sin.sin_family = AF_INET;
253 sin.sin_port = htons(port);
254 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
255 if (connect(sock, (struct sockaddr *)&sin, sizeof(sin))) {
256 fprintf(stderr, "Failed to connect to '%s' port '%d': %s\n", host, port, strerror(errno));
260 astf = fdopen(sock, "r+");
262 fprintf(stderr, "fdopen failed: %s\n", strerror(errno));
269 static char *get_line(void)
271 static char buf[1024];
272 if (fgets(buf, sizeof(buf), astf)) {
273 while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
274 buf[strlen(buf) - 1] = '\0';
280 /*! Login to the asterisk manager interface */
281 static int login_asterisk(void)
285 if (!(welcome = get_line())) {
286 fprintf(stderr, "disconnected (1)\n");
292 "Secret: %s\r\n\r\n", user, pass);
293 if (!(welcome = get_line())) {
294 fprintf(stderr, "disconnected (2)\n");
297 if (strcasecmp(welcome, "Response: Success")) {
298 fprintf(stderr, "login failed ('%s')\n", welcome);
301 /* Eat the rest of the event */
302 while((resp = get_line()) && strlen(resp));
304 fprintf(stderr, "disconnected (3)\n");
308 "Action: Status\r\n\r\n");
309 if (!(welcome = get_line())) {
310 fprintf(stderr, "disconnected (4)\n");
313 if (strcasecmp(welcome, "Response: Success")) {
314 fprintf(stderr, "status failed ('%s')\n", welcome);
317 /* Eat the rest of the event */
318 while((resp = get_line()) && strlen(resp));
320 fprintf(stderr, "disconnected (5)\n");
326 static struct channel *find_channel(char *channel)
330 struct channel *chan;
331 strncpy(tmp, channel, sizeof(tmp) - 1);
332 s = strchr(tmp, '/');
341 printf("Searching for '%s' tech, '%s' location\n", tmp, s);
344 if (!strcasecmp(chan->tech, tmp) && !strcasecmp(chan->location, s)) {
346 printf("Found '%s'/'%s'\n", chan->tech, chan->location);
357 static int getvol(void)
361 if (ioctl(mixfd, MIXER_READ(mixchan), &vol)) {
363 static float getvol(void)
365 float volumeL, volumeR, vol;
367 AudioDeviceID device;
370 AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
371 AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
372 AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
374 size = sizeof(device);
375 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
376 size = sizeof(channels);
378 err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
382 VolumeAddr.mElement = channels[0];
383 err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeL);
386 VolumeAddr.mElement = channels[1];
387 err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeR);
390 vol = (volumeL < volumeR) ? volumeR : volumeL;
393 fprintf(stderr, "Unable to read mixer volume: %s\n", strerror(errno));
400 static int setvol(int vol)
402 static int setvol(float vol)
406 if (ioctl(mixfd, MIXER_WRITE(mixchan), &vol)) {
411 AudioDeviceID device;
414 AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
415 AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
416 AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
418 size = sizeof(device);
419 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
420 size = sizeof(channels);
421 err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
424 VolumeAddr.mElement = channels[0];
425 err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeL);
428 VolumeAddr.mElement = channels[1];
429 err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeR);
434 fprintf(stderr, "Unable to write mixer volume: %s\n", strerror(errno));
442 static int oldvol = 0;
443 static int mutevol = 0;
445 static float oldvol = 0;
446 static float mutevol = 0;
450 static int mutedlevel(int orig, int level)
454 l = (float)(level) * (float)(l) / 100.0;
455 r = (float)(level) * (float)(r) / 100.0;
459 static float mutedlevel(float orig, float level)
462 master = level * master / 100.0;
468 static void mute(void)
489 for (x=start;x>=mutelevel;x-=stepsize) {
490 mutevol = mutedlevel(vol, x);
495 mutevol = mutedlevel(vol, mutelevel);
499 printf("Mute from '%f' to '%f'!\n", oldvol, mutevol);
501 printf("Mute from '%04x' to '%04x'!\n", oldvol, mutevol);
506 static void unmute(void)
520 printf("Unmute from '%f' (should be '%f') to '%f'!\n", vol, mutevol, oldvol);
522 if (vol == mutevol) {
524 printf("Unmute from '%04x' (should be '%04x') to '%04x'!\n", vol, mutevol, oldvol);
525 if ((int)vol == mutevol) {
535 for (x=start;x<100;x+=stepsize) {
536 mutevol = mutedlevel(oldvol, x);
543 printf("Whoops, it's already been changed!\n");
547 static void check_mute(void)
550 struct channel *chan;
559 if (offhook && !muted)
561 else if (!offhook && muted)
565 static void delete_sub(struct channel *chan, char *name)
567 struct subchannel *sub, *prev;
571 if (!strcasecmp(sub->name, name)) {
573 prev->next = sub->next;
575 chan->subs = sub->next;
585 static void append_sub(struct channel *chan, char *name)
587 struct subchannel *sub;
590 if (!strcasecmp(sub->name, name))
594 sub = malloc(sizeof(struct subchannel));
596 memset(sub, 0, sizeof(struct subchannel));
597 if (!(sub->name = strdup(name))) {
601 sub->next = chan->subs;
606 static void hangup_chan(char *channel)
608 struct channel *chan;
610 printf("Hangup '%s'\n", channel);
611 chan = find_channel(channel);
613 delete_sub(chan, channel);
617 static void offhook_chan(char *channel)
619 struct channel *chan;
621 printf("Offhook '%s'\n", channel);
622 chan = find_channel(channel);
624 append_sub(chan, channel);
628 static int wait_event(void)
632 char channel[120]="";
633 char oldname[120]="";
634 char newname[120]="";
638 fprintf(stderr, "disconnected (6)\n");
641 if (!strncasecmp(resp, "Event: ", strlen("Event: "))) {
642 strncpy(event, resp + strlen("Event: "), sizeof(event) - 1);
643 /* Consume the rest of the non-event */
644 while((resp = get_line()) && strlen(resp)) {
645 if (!strncasecmp(resp, "Channel: ", strlen("Channel: ")))
646 strncpy(channel, resp + strlen("Channel: "), sizeof(channel) - 1);
647 if (!strncasecmp(resp, "Newname: ", strlen("Newname: ")))
648 strncpy(newname, resp + strlen("Newname: "), sizeof(newname) - 1);
649 if (!strncasecmp(resp, "Oldname: ", strlen("Oldname: ")))
650 strncpy(oldname, resp + strlen("Oldname: "), sizeof(oldname) - 1);
652 if (strlen(channel)) {
653 if (!strcasecmp(event, "Hangup"))
654 hangup_chan(channel);
656 offhook_chan(channel);
658 if (strlen(newname) && strlen(oldname)) {
659 if (!strcasecmp(event, "Rename")) {
660 hangup_chan(oldname);
661 offhook_chan(newname);
665 /* Consume the rest of the non-event */
666 while((resp = get_line()) && strlen(resp));
669 fprintf(stderr, "disconnected (7)\n");
675 static void usage(void)
677 printf("Usage: muted [-f] [-d]\n"
678 " -f : Do not fork\n"
679 " -d : Debug (implies -f)\n");
682 int main(int argc, char *argv[])
685 while((x = getopt(argc, argv, "fhd")) > 0) {
707 if (connect_asterisk()) {
713 if (login_asterisk()) {
720 #ifdef HAVE_WORKING_FORK
722 #ifndef HAVE_SBIN_LAUNCHD
723 if (daemon(0,0) < 0) {
724 fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
728 const char *found = NULL, *paths[] = {
729 "/Library/LaunchAgents/org.asterisk.muted.plist",
730 "/Library/LaunchDaemons/org.asterisk.muted.plist",
731 "contrib/init.d/org.asterisk.muted.plist",
732 "<path-to-asterisk-source>/contrib/init.d/org.asterisk.muted.plist" };
735 struct passwd *pwd = getpwuid(getuid());
738 snprintf(userpath, sizeof(userpath), "%s%s", pwd->pw_dir, paths[0]);
739 if (!stat(userpath, &unused)) {
744 for (i = 0; i < 3; i++) {
745 if (!stat(paths[i], &unused)) {
752 fprintf(stderr, "Mac OS X detected. Use 'launchctl load -w %s' to launch.\n", found ? found : paths[3]);
754 #endif /* !defined(HAVE_SBIN_LAUNCHD */
760 while(connect_asterisk()) {
763 if (login_asterisk()) {