[gaim-migrate @ 3063]
[pidgin-git.git] / src / sound.c
blob08d5f3fababae3c1639cfe11929eac8681ab1cbb
1 /*
2 * gaim
4 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/time.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
36 #ifdef ESD_SOUND
37 #include <esd.h>
38 #endif
40 #ifdef ARTSC_SOUND
41 #include <artsc.h>
42 #endif
44 #ifdef NAS_SOUND
45 #include <audio/audiolib.h>
46 #endif
48 #include "gaim.h"
49 #include "sounds/BuddyArrive.h"
50 #include "sounds/BuddyLeave.h"
51 #include "sounds/Send.h"
52 #include "sounds/Receive.h"
53 #include "sounds/RedAlert.h"
55 static int check_dev(char *dev)
57 struct stat stat_buf;
58 uid_t user = getuid();
59 gid_t group = getgid(), other_groups[32];
60 int i, numgroups;
62 if ((numgroups = getgroups(32, other_groups)) == -1)
63 return 0;
64 if (stat(dev, &stat_buf))
65 return 0;
66 if (user == stat_buf.st_uid && stat_buf.st_mode & S_IWUSR)
67 return 1;
68 if (stat_buf.st_mode & S_IWGRP) {
69 if (group == stat_buf.st_gid)
70 return 1;
71 for (i = 0; i < numgroups; i++)
72 if (other_groups[i] == stat_buf.st_gid)
73 return 1;
75 if (stat_buf.st_mode & S_IWOTH)
76 return 1;
77 return 0;
81 static void play_audio(unsigned char *data, int size)
83 int fd;
85 fd = open("/dev/audio", O_WRONLY | O_EXCL | O_NDELAY);
86 if (fd < 0)
87 return;
88 write(fd, data, size);
89 close(fd);
92 static void play_audio_file(char *file)
94 /* here we can assume that we can write to /dev/audio */
95 char *buf;
96 struct stat info;
97 int fd = open(file, O_RDONLY);
98 if (fd <= 0) {
99 return;
101 fstat(fd, &info);
102 if (info.st_size < 24)
103 return;
104 buf = malloc(info.st_size + 1);
105 read(fd, buf, 24);
106 read(fd, buf, info.st_size - 24);
107 close(fd);
109 fd = open("/dev/audio", O_WRONLY | O_EXCL | O_NDELAY);
110 if (fd < 0) {
111 free(buf);
112 return;
114 write(fd, buf, info.st_size - 24);
115 free(buf);
116 close(fd);
119 static int can_play_audio()
121 return check_dev("/dev/audio");
125 #if defined(ESD_SOUND) || defined(ARTSC_SOUND)
128 ** This routine converts from ulaw to 16 bit linear.
130 ** Craig Reese: IDA/Supercomputing Research Center
131 ** 29 September 1989
133 ** References:
134 ** 1) CCITT Recommendation G.711 (very difficult to follow)
135 ** 2) MIL-STD-188-113,"Interoperability and Performance Standards
136 ** for Analog-to_Digital Conversion Techniques,"
137 ** 17 February 1987
139 ** Input: 8 bit ulaw sample
140 ** Output: signed 16 bit linear sample
141 ** Z-note -- this is from libaudiofile. Thanks guys!
144 static int _af_ulaw2linear(unsigned char ulawbyte)
146 static int exp_lut[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
147 int sign, exponent, mantissa, sample;
149 ulawbyte = ~ulawbyte;
150 sign = (ulawbyte & 0x80);
151 exponent = (ulawbyte >> 4) & 0x07;
152 mantissa = ulawbyte & 0x0F;
153 sample = exp_lut[exponent] + (mantissa << (exponent + 3));
154 if (sign != 0)
155 sample = -sample;
157 return (sample);
160 #endif
162 #ifdef ESD_SOUND
164 int esd_fd;
166 static int play_esd(unsigned char *data, int size)
168 int i;
169 guint16 *lineardata;
171 lineardata = g_malloc(size * 2);
173 for (i = 0; i < size; i++)
174 lineardata[i] = _af_ulaw2linear(data[i]);
176 write(esd_fd, lineardata, size * 2);
178 close(esd_fd);
179 g_free(lineardata);
181 return 1;
185 static int can_play_esd()
187 esd_format_t format = ESD_BITS16 | ESD_STREAM | ESD_PLAY | ESD_MONO;
189 esd_fd = esd_play_stream(format, 8012, NULL, "gaim");
191 if (esd_fd < 0) {
192 return 0;
195 return 1;
198 #endif
200 #ifdef ARTSC_SOUND
202 static int play_artsc(unsigned char *data, int size)
204 arts_stream_t stream;
205 guint16 *lineardata;
206 int result = 1;
207 int error;
208 int i;
210 lineardata = g_malloc(size * 2);
212 for (i = 0; i < size; i++) {
213 lineardata[i] = _af_ulaw2linear(data[i]);
216 stream = arts_play_stream(8012, 16, 1, "gaim");
218 error = arts_write(stream, lineardata, size);
219 if (error < 0) {
220 result = 0;
223 arts_close_stream(stream);
225 g_free(lineardata);
227 arts_free();
229 return result;
232 static int can_play_artsc()
234 int error;
236 error = arts_init();
237 if (error < 0)
238 return 0;
240 return 1;
243 static int artsc_play_file(char *file)
245 struct stat stat_buf;
246 unsigned char *buf = NULL;
247 int result = 0;
248 int fd = -1;
250 if (!can_play_artsc())
251 return 0;
253 fd = open(file, O_RDONLY);
254 if (fd < 0)
255 return 0;
257 if (fstat(fd, &stat_buf)) {
258 close(fd);
259 return 0;
262 if (!stat_buf.st_size) {
263 close(fd);
264 return 0;
267 buf = g_malloc(stat_buf.st_size);
268 if (!buf) {
269 close(fd);
270 return 0;
273 if (read(fd, buf, stat_buf.st_size) < 0) {
274 g_free(buf);
275 close(fd);
276 return 0;
279 result = play_artsc(buf, stat_buf.st_size);
281 g_free(buf);
282 close(fd);
283 return result;
286 #endif /* ARTSC_SOUND */
288 #ifdef NAS_SOUND
290 char nas_server[] = "localhost";
291 AuServer *nas_serv = NULL;
293 static AuBool NasEventHandler(AuServer * aud, AuEvent * ev, AuEventHandlerRec * handler)
295 AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev;
297 if (ev->type == AuEventTypeElementNotify) {
298 switch (event->kind) {
299 case AuElementNotifyKindState:
300 switch (event->cur_state) {
301 case AuStateStop:
302 _exit(0);
304 break;
307 return AuTrue;
311 static int play_nas(unsigned char *data, int size)
313 AuDeviceID device = AuNone;
314 AuFlowID flow;
315 AuElement elements[3];
316 int i, n, w;
318 /* look for an output device */
319 for (i = 0; i < AuServerNumDevices(nas_serv); i++) {
320 if ((AuDeviceKind(AuServerDevice(nas_serv, i)) ==
321 AuComponentKindPhysicalOutput) &&
322 AuDeviceNumTracks(AuServerDevice(nas_serv, i)) == 1) {
323 device = AuDeviceIdentifier(AuServerDevice(nas_serv, i));
324 break;
328 if (device == AuNone)
329 return 0;
331 if (!(flow = AuCreateFlow(nas_serv, NULL)))
332 return 0;
335 AuMakeElementImportClient(&elements[0], 8012, AuFormatULAW8, 1, AuTrue, size, size / 2, 0, NULL);
336 AuMakeElementExportDevice(&elements[1], 0, device, 8012, AuUnlimitedSamples, 0, NULL);
337 AuSetElements(nas_serv, flow, AuTrue, 2, elements, NULL);
339 AuStartFlow(nas_serv, flow, NULL);
341 AuWriteElement(nas_serv, flow, 0, size, data, AuTrue, NULL);
343 AuRegisterEventHandler(nas_serv, AuEventHandlerIDMask, 0, flow, NasEventHandler, NULL);
345 while (1) {
346 AuHandleEvents(nas_serv);
349 return 1;
352 static int can_play_nas()
354 if ((nas_serv = AuOpenServer(NULL, 0, NULL, 0, NULL, NULL)))
355 return 1;
356 return 0;
359 static int play_nas_file(char *file)
361 struct stat stat_buf;
362 char *buf;
363 int ret;
364 int fd = open(file, O_RDONLY);
365 if (fd <= 0)
366 return 0;
368 if (!can_play_nas())
369 return 0;
371 if (stat(file, &stat_buf))
372 return 0;
374 if (!stat_buf.st_size)
375 return 0;
377 buf = malloc(stat_buf.st_size);
378 read(fd, buf, stat_buf.st_size);
379 ret = play_nas(buf, stat_buf.st_size);
380 free(buf);
381 return ret;
384 #endif
386 void play_file(char *filename)
388 int pid;
390 #ifdef _WIN32
391 return;
392 #endif
394 if (sound_options & OPT_SOUND_BEEP) {
395 gdk_beep();
396 return;
399 pid = fork();
401 if (pid < 0)
402 return;
403 else if (pid == 0) {
404 alarm(30);
406 if ((sound_options & OPT_SOUND_CMD) && sound_cmd[0]) {
407 char *args[4];
408 char command[4096];
410 g_snprintf(command, sizeof(command), sound_cmd, filename);
412 args[0] = "sh";
413 args[1] = "-c";
414 args[2] = command;
415 args[3] = NULL;
416 execvp(args[0], args);
417 _exit(0);
419 #ifdef ESD_SOUND
420 else if (sound_options & OPT_SOUND_ESD) {
421 if (esd_play_file(NULL, filename, 1))
422 _exit(0);
424 #endif
426 #ifdef ARTSC_SOUND
427 else if (sound_options & OPT_SOUND_ARTSC) {
428 if (artsc_play_file(filename))
429 _exit(0);
431 #endif
433 #ifdef NAS_SOUND
434 else if (sound_options & OPT_SOUND_NAS) {
435 if (play_nas_file(filename))
436 _exit(0);
438 #endif
439 else if ((sound_options & OPT_SOUND_NORMAL) &&
440 can_play_audio()) {
441 play_audio_file(filename);
442 _exit(0);
445 _exit(0);
446 } else {
447 g_timeout_add(100, clean_pid, NULL);
451 void play(unsigned char *data, int size)
453 int pid;
455 #ifdef _WIN32
456 return;
457 #endif
459 if (sound_options & OPT_SOUND_BEEP) {
460 gdk_beep();
461 return;
464 else if ((sound_options & OPT_SOUND_CMD) && sound_cmd[0]) {
465 debug_printf("can't play internal sound with external command -- skipping\n");
466 return;
469 pid = fork();
471 if (pid < 0)
472 return;
473 else if (pid == 0) {
474 alarm(30);
476 #ifdef ESD_SOUND
477 /* ESD is our player of choice. Are we OK to
478 * go there? */
479 if (sound_options & OPT_SOUND_ESD) {
480 if (can_play_esd()) {
481 if (play_esd(data, size))
482 _exit(0);
485 #endif
487 #ifdef ARTSC_SOUND
488 /* ArtsC is the new second choice. */
489 if (sound_options & OPT_SOUND_ARTSC) {
490 if (can_play_artsc()) {
491 if (play_artsc(data, size))
492 _exit(0);
495 #endif
497 #ifdef NAS_SOUND
498 /* NAS is our second choice setup. */
499 if (sound_options & OPT_SOUND_NAS) {
500 if (can_play_nas()) {
501 if (play_nas(data, size))
502 _exit(0);
505 #endif
507 /* Lastly, we can try just plain old /dev/audio */
508 if (sound_options & OPT_SOUND_NORMAL) {
509 if (can_play_audio()) {
510 play_audio(data, size);
511 _exit(0);
515 _exit(0);
516 } else {
517 g_timeout_add(100, clean_pid, NULL);
521 extern int logins_not_muted;
523 void play_sound(int sound)
526 if (awaymessage && !(sound_options & OPT_SOUND_WHEN_AWAY))
527 return;
529 switch (sound) {
530 case BUDDY_ARRIVE:
531 if ((sound_options & OPT_SOUND_LOGIN) && logins_not_muted) {
532 if (sound_file[BUDDY_ARRIVE]) {
533 play_file(sound_file[BUDDY_ARRIVE]);
534 } else {
535 play(BuddyArrive, sizeof(BuddyArrive));
538 break;
539 case BUDDY_LEAVE:
540 if (sound_options & OPT_SOUND_LOGOUT) {
541 if (sound_file[BUDDY_LEAVE]) {
542 play_file(sound_file[BUDDY_LEAVE]);
543 } else {
544 play(BuddyLeave, sizeof(BuddyLeave));
547 break;
548 case FIRST_RECEIVE:
549 if (sound_options & OPT_SOUND_FIRST_RCV) {
550 if (sound_file[FIRST_RECEIVE]) {
551 play_file(sound_file[FIRST_RECEIVE]);
552 } else {
553 play(Receive, sizeof(Receive));
556 break;
557 case RECEIVE:
558 if (sound_options & OPT_SOUND_RECV) {
559 if (sound_file[RECEIVE]) {
560 play_file(sound_file[RECEIVE]);
561 } else {
562 play(Receive, sizeof(Receive));
565 break;
566 case SEND:
567 if (sound_options & OPT_SOUND_SEND) {
568 if (sound_file[SEND]) {
569 play_file(sound_file[SEND]);
570 } else {
571 play(Send, sizeof(Send));
574 break;
575 case CHAT_JOIN:
576 if (sound_options & OPT_SOUND_CHAT_JOIN) {
577 if (sound_file[CHAT_JOIN]) {
578 play_file(sound_file[CHAT_JOIN]);
579 } else {
580 play(BuddyArrive, sizeof(BuddyArrive));
583 break;
584 case CHAT_LEAVE:
585 if (sound_options & OPT_SOUND_CHAT_PART) {
586 if (sound_file[CHAT_LEAVE]) {
587 play_file(sound_file[CHAT_LEAVE]);
588 } else {
589 play(BuddyLeave, sizeof(BuddyLeave));
592 break;
593 case CHAT_YOU_SAY:
594 if (sound_options & OPT_SOUND_CHAT_YOU_SAY) {
595 if (sound_file[CHAT_YOU_SAY]) {
596 play_file(sound_file[CHAT_YOU_SAY]);
597 } else {
598 play(Send, sizeof(Send));
601 break;
602 case CHAT_SAY:
603 if (sound_options & OPT_SOUND_CHAT_SAY) {
604 if (sound_file[CHAT_SAY]) {
605 play_file(sound_file[CHAT_SAY]);
606 } else {
607 play(Receive, sizeof(Receive));
610 break;
611 case POUNCE_DEFAULT:
612 if (sound_file[POUNCE_DEFAULT]) {
613 play_file(sound_file[POUNCE_DEFAULT]);
614 } else {
615 play(RedAlert, sizeof(RedAlert));
617 break;