[gaim-migrate @ 3159]
[pidgin-git.git] / src / sound.c
blobff91ebc20bf429418ecc1edffeb1d745fd844439
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 /* label and opt are null for the buddy pounce because it's configured *
56 * per pounce. NULL option means it doesn't get displayed in the sound *
57 * preferences box */
58 struct sound_struct sounds[NUM_SOUNDS] = {
59 N_("Buddy logs in"), OPT_SOUND_LOGIN, BuddyArrive, sizeof(BuddyArrive),
60 N_("Buddy logs out"), OPT_SOUND_LOGOUT, BuddyLeave, sizeof(BuddyLeave),
61 N_("Message received"), OPT_SOUND_RECV, Receive, sizeof(Receive),
62 N_("Message received begins conversation"), OPT_SOUND_FIRST_RCV, Receive, sizeof(Receive),
63 N_("Message sent"), OPT_SOUND_SEND, Send, sizeof(Send),
64 N_("Person enters chat"), OPT_SOUND_CHAT_JOIN, BuddyArrive, sizeof(BuddyArrive),
65 N_("Person leaves chat"), OPT_SOUND_CHAT_PART, BuddyLeave, sizeof(BuddyLeave),
66 N_("You talk in chat"), OPT_SOUND_CHAT_YOU_SAY, Send, sizeof(Send),
67 N_("Others talk in chat"), OPT_SOUND_CHAT_SAY, Receive, sizeof(Receive),
68 NULL, 0, RedAlert, sizeof(RedAlert)
71 static int check_dev(char *dev)
73 struct stat stat_buf;
74 uid_t user = getuid();
75 gid_t group = getgid(), other_groups[32];
76 int i, numgroups;
78 if ((numgroups = getgroups(32, other_groups)) == -1)
79 return 0;
80 if (stat(dev, &stat_buf))
81 return 0;
82 if (user == stat_buf.st_uid && stat_buf.st_mode & S_IWUSR)
83 return 1;
84 if (stat_buf.st_mode & S_IWGRP) {
85 if (group == stat_buf.st_gid)
86 return 1;
87 for (i = 0; i < numgroups; i++)
88 if (other_groups[i] == stat_buf.st_gid)
89 return 1;
91 if (stat_buf.st_mode & S_IWOTH)
92 return 1;
93 return 0;
97 static void play_audio(unsigned char *data, int size)
99 int fd;
101 fd = open("/dev/audio", O_WRONLY | O_EXCL | O_NDELAY);
102 if (fd < 0)
103 return;
104 write(fd, data, size);
105 close(fd);
108 static void play_audio_file(char *file)
110 /* here we can assume that we can write to /dev/audio */
111 char *buf;
112 struct stat info;
113 int fd = open(file, O_RDONLY);
114 if (fd <= 0) {
115 return;
117 fstat(fd, &info);
118 if (info.st_size < 24)
119 return;
120 buf = malloc(info.st_size + 1);
121 read(fd, buf, 24);
122 read(fd, buf, info.st_size - 24);
123 close(fd);
125 fd = open("/dev/audio", O_WRONLY | O_EXCL | O_NDELAY);
126 if (fd < 0) {
127 free(buf);
128 return;
130 write(fd, buf, info.st_size - 24);
131 free(buf);
132 close(fd);
135 static int can_play_audio()
137 return check_dev("/dev/audio");
141 #if defined(ESD_SOUND) || defined(ARTSC_SOUND)
144 ** This routine converts from ulaw to 16 bit linear.
146 ** Craig Reese: IDA/Supercomputing Research Center
147 ** 29 September 1989
149 ** References:
150 ** 1) CCITT Recommendation G.711 (very difficult to follow)
151 ** 2) MIL-STD-188-113,"Interoperability and Performance Standards
152 ** for Analog-to_Digital Conversion Techniques,"
153 ** 17 February 1987
155 ** Input: 8 bit ulaw sample
156 ** Output: signed 16 bit linear sample
157 ** Z-note -- this is from libaudiofile. Thanks guys!
160 static int _af_ulaw2linear(unsigned char ulawbyte)
162 static int exp_lut[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
163 int sign, exponent, mantissa, sample;
165 ulawbyte = ~ulawbyte;
166 sign = (ulawbyte & 0x80);
167 exponent = (ulawbyte >> 4) & 0x07;
168 mantissa = ulawbyte & 0x0F;
169 sample = exp_lut[exponent] + (mantissa << (exponent + 3));
170 if (sign != 0)
171 sample = -sample;
173 return (sample);
176 #endif
178 #ifdef ESD_SOUND
180 int esd_fd;
182 static int play_esd(unsigned char *data, int size)
184 int i;
185 guint16 *lineardata;
187 lineardata = g_malloc(size * 2);
189 for (i = 0; i < size; i++)
190 lineardata[i] = _af_ulaw2linear(data[i]);
192 write(esd_fd, lineardata, size * 2);
194 close(esd_fd);
195 g_free(lineardata);
197 return 1;
201 static int can_play_esd()
203 esd_format_t format = ESD_BITS16 | ESD_STREAM | ESD_PLAY | ESD_MONO;
205 esd_fd = esd_play_stream(format, 8012, NULL, "gaim");
207 if (esd_fd < 0) {
208 return 0;
211 return 1;
214 #endif
216 #ifdef ARTSC_SOUND
218 static int play_artsc(unsigned char *data, int size)
220 arts_stream_t stream;
221 guint16 *lineardata;
222 int result = 1;
223 int error;
224 int i;
226 lineardata = g_malloc(size * 2);
228 for (i = 0; i < size; i++) {
229 lineardata[i] = _af_ulaw2linear(data[i]);
232 stream = arts_play_stream(8012, 16, 1, "gaim");
234 error = arts_write(stream, lineardata, size);
235 if (error < 0) {
236 result = 0;
239 arts_close_stream(stream);
241 g_free(lineardata);
243 arts_free();
245 return result;
248 static int can_play_artsc()
250 int error;
252 error = arts_init();
253 if (error < 0)
254 return 0;
256 return 1;
259 static int artsc_play_file(char *file)
261 struct stat stat_buf;
262 unsigned char *buf = NULL;
263 int result = 0;
264 int fd = -1;
266 if (!can_play_artsc())
267 return 0;
269 fd = open(file, O_RDONLY);
270 if (fd < 0)
271 return 0;
273 if (fstat(fd, &stat_buf)) {
274 close(fd);
275 return 0;
278 if (!stat_buf.st_size) {
279 close(fd);
280 return 0;
283 buf = g_malloc(stat_buf.st_size);
284 if (!buf) {
285 close(fd);
286 return 0;
289 if (read(fd, buf, stat_buf.st_size) < 0) {
290 g_free(buf);
291 close(fd);
292 return 0;
295 result = play_artsc(buf, stat_buf.st_size);
297 g_free(buf);
298 close(fd);
299 return result;
302 #endif /* ARTSC_SOUND */
304 #ifdef NAS_SOUND
306 char nas_server[] = "localhost";
307 AuServer *nas_serv = NULL;
309 static AuBool NasEventHandler(AuServer * aud, AuEvent * ev, AuEventHandlerRec * handler)
311 AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev;
313 if (ev->type == AuEventTypeElementNotify) {
314 switch (event->kind) {
315 case AuElementNotifyKindState:
316 switch (event->cur_state) {
317 case AuStateStop:
318 _exit(0);
320 break;
323 return AuTrue;
327 static int play_nas(unsigned char *data, int size)
329 AuDeviceID device = AuNone;
330 AuFlowID flow;
331 AuElement elements[3];
332 int i, n, w;
334 /* look for an output device */
335 for (i = 0; i < AuServerNumDevices(nas_serv); i++) {
336 if ((AuDeviceKind(AuServerDevice(nas_serv, i)) ==
337 AuComponentKindPhysicalOutput) &&
338 AuDeviceNumTracks(AuServerDevice(nas_serv, i)) == 1) {
339 device = AuDeviceIdentifier(AuServerDevice(nas_serv, i));
340 break;
344 if (device == AuNone)
345 return 0;
347 if (!(flow = AuCreateFlow(nas_serv, NULL)))
348 return 0;
351 AuMakeElementImportClient(&elements[0], 8012, AuFormatULAW8, 1, AuTrue, size, size / 2, 0, NULL);
352 AuMakeElementExportDevice(&elements[1], 0, device, 8012, AuUnlimitedSamples, 0, NULL);
353 AuSetElements(nas_serv, flow, AuTrue, 2, elements, NULL);
355 AuStartFlow(nas_serv, flow, NULL);
357 AuWriteElement(nas_serv, flow, 0, size, data, AuTrue, NULL);
359 AuRegisterEventHandler(nas_serv, AuEventHandlerIDMask, 0, flow, NasEventHandler, NULL);
361 while (1) {
362 AuHandleEvents(nas_serv);
365 return 1;
368 static int can_play_nas()
370 if ((nas_serv = AuOpenServer(NULL, 0, NULL, 0, NULL, NULL)))
371 return 1;
372 return 0;
375 static int play_nas_file(char *file)
377 struct stat stat_buf;
378 char *buf;
379 int ret;
380 int fd = open(file, O_RDONLY);
381 if (fd <= 0)
382 return 0;
384 if (!can_play_nas())
385 return 0;
387 if (stat(file, &stat_buf))
388 return 0;
390 if (!stat_buf.st_size)
391 return 0;
393 buf = malloc(stat_buf.st_size);
394 read(fd, buf, stat_buf.st_size);
395 ret = play_nas(buf, stat_buf.st_size);
396 free(buf);
397 return ret;
400 #endif
402 void play_file(char *filename)
404 int pid;
406 #ifdef _WIN32
407 return;
408 #endif
410 if (sound_options & OPT_SOUND_BEEP) {
411 gdk_beep();
412 return;
415 else if (sound_options & OPT_SOUND_NORMAL) {
416 debug_printf("attempting to play audio file with internal method -- this is unlikely to work\n");
419 pid = fork();
421 if (pid < 0)
422 return;
423 else if (pid == 0) {
424 alarm(30);
426 if ((sound_options & OPT_SOUND_CMD) && sound_cmd[0]) {
427 char *args[4];
428 char command[4096];
430 g_snprintf(command, sizeof(command), sound_cmd, filename);
432 args[0] = "sh";
433 args[1] = "-c";
434 args[2] = command;
435 args[3] = NULL;
436 execvp(args[0], args);
437 _exit(0);
439 #ifdef ESD_SOUND
440 else if (sound_options & OPT_SOUND_ESD) {
441 if (esd_play_file(NULL, filename, 1))
442 _exit(0);
444 #endif
446 #ifdef ARTSC_SOUND
447 else if (sound_options & OPT_SOUND_ARTSC) {
448 if (artsc_play_file(filename))
449 _exit(0);
451 #endif
453 #ifdef NAS_SOUND
454 else if (sound_options & OPT_SOUND_NAS) {
455 if (play_nas_file(filename))
456 _exit(0);
458 #endif
459 else if ((sound_options & OPT_SOUND_NORMAL) &&
460 can_play_audio()) {
461 play_audio_file(filename);
462 _exit(0);
465 _exit(0);
469 void play(unsigned char *data, int size)
471 int pid;
473 #ifdef _WIN32
474 return;
475 #endif
477 if (sound_options & OPT_SOUND_BEEP) {
478 gdk_beep();
479 return;
482 else if ((sound_options & OPT_SOUND_CMD) && sound_cmd[0]) {
483 debug_printf("can't play internal sound with external command -- skipping\n");
484 return;
487 pid = fork();
489 if (pid < 0)
490 return;
491 else if (pid == 0) {
492 alarm(30);
494 #ifdef ESD_SOUND
495 /* ESD is our player of choice. Are we OK to
496 * go there? */
497 if (sound_options & OPT_SOUND_ESD) {
498 if (can_play_esd()) {
499 if (play_esd(data, size))
500 _exit(0);
503 #endif
505 #ifdef ARTSC_SOUND
506 /* ArtsC is the new second choice. */
507 if (sound_options & OPT_SOUND_ARTSC) {
508 if (can_play_artsc()) {
509 if (play_artsc(data, size))
510 _exit(0);
513 #endif
515 #ifdef NAS_SOUND
516 /* NAS is our second choice setup. */
517 if (sound_options & OPT_SOUND_NAS) {
518 if (can_play_nas()) {
519 if (play_nas(data, size))
520 _exit(0);
523 #endif
525 /* Lastly, we can try just plain old /dev/audio */
526 if (sound_options & OPT_SOUND_NORMAL) {
527 if (can_play_audio()) {
528 play_audio(data, size);
529 _exit(0);
533 _exit(0);
537 extern int logins_not_muted;
539 void play_sound(int sound)
541 if (awaymessage && !(sound_options & OPT_SOUND_WHEN_AWAY))
542 return;
544 if ((sound == SND_BUDDY_ARRIVE) && !logins_not_muted)
545 return;
547 if (sound >= NUM_SOUNDS) {
548 debug_printf("sorry old fruit... can't say I know that sound: ", sound, "\n");
549 return;
552 /* check NULL for sounds that don't have an option, ie buddy pounce */
553 if ((sound_options & sounds[sound].opt) || (sounds[sound].opt == 0)) {
554 if (sound_file[sound]) {
555 play_file(sound_file[sound]);
556 } else {
557 play(sounds[sound].snd, sounds[sound].snd_size);