/* Evil evil evil hack to get OSS apps to cooperate with esd * Copyright (C) 1998, 1999 Manish Singh <yosh@gimp.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* #define DSP_DEBUG */ /* This lets you run multiple instances of x11amp by setting the X11AMPNUM environment variable. Only works on glibc2. */ /* #define MULTIPLE_X11AMP */ #if defined(__GNUC__) && !defined(__STRICT_ANSI__) #ifdef DSP_DEBUG #define DPRINTF(format, args...) printf(format, ## args) #else #define DPRINTF(format, args...) #endif #include "config.h" #include <dlfcn.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #ifdef HAVE_MACHINE_SOUNDCARD_H # include <machine/soundcard.h> #else # ifdef HAVE_SOUNDCARD_H # include <soundcard.h> # else # include <sys/soundcard.h> # endif #endif #include "esd.h" #define REAL_LIBC RTLD_NEXT #ifdef __FreeBSD__ typedef unsigned long request_t; #else typedef int request_t; #endif static int sndfd = -1, mixfd = -1; static int settings = 0, done = 0; static char *ident = NULL, *mixer = NULL; static int use_mixer = 0; #define OSS_VOLUME_BASE 50 #define ESD_VOL_TO_OSS(left, right) (short int) \ (((OSS_VOLUME_BASE * (right) / ESD_VOLUME_BASE) << 8) | \ (OSS_VOLUME_BASE * (left) / ESD_VOLUME_BASE)) #define OSS_VOL_TO_ESD_LEFT(vol) \ (ESD_VOLUME_BASE * (vol & 0xff) / OSS_VOLUME_BASE) #define OSS_VOL_TO_ESD_RIGHT(vol) \ (ESD_VOLUME_BASE * ((vol >> 8) & 0xff) / OSS_VOLUME_BASE) static void get_volume (int *left, int *right) { int vol; if (read (mixfd, &vol, sizeof (vol)) != sizeof (vol)) *left = *right = ESD_VOLUME_BASE; else { *left = OSS_VOL_TO_ESD_LEFT (vol); *right = OSS_VOL_TO_ESD_RIGHT (vol); } } static void set_volume (int left, int right) { int vol = ESD_VOL_TO_OSS (left, right); write (mixfd, &vol, sizeof (vol)); } static void dsp_init (void) { if (!ident) { char *str = getenv ("ESDDSP_NAME"); ident = malloc (ESD_NAME_MAX); strncpy (ident, (str ? str : "esddsp"), ESD_NAME_MAX); if (getenv ("ESDDSP_MIXER")) { use_mixer = 1; str = getenv ("HOME"); if (str) { mixer = malloc (strlen (str) + strlen (ident) + 10); sprintf (mixer, "%s/.esddsp_%s", str, ident); } else { fprintf (stderr, "esddsp: can't get home directory\n"); exit (1); } DPRINTF ("mixer settings file: %s\n", mixer); } } } static void mix_init (int *esd, int *player) { esd_info_t *all_info; esd_player_info_t *player_info; if (*esd < 0 && (*esd = esd_open_sound (NULL)) < 0) return; if (*player < 0) { if (all_info = esd_get_all_info (*esd)) { for (player_info = all_info->player_list; player_info; player_info = player_info->next) if (!strcmp(player_info->name, ident)) { *player = player_info->source_id; break; } esd_free_all_info (all_info); } } } int open (const char *pathname, int flags, ...) { static int (*func) (const char *, int, mode_t) = NULL; va_list args; mode_t mode; if (!func) func = (int (*) (const char *, int, mode_t)) dlsym (REAL_LIBC, "open"); dsp_init (); va_start (args, flags); mode = va_arg (args, mode_t); va_end (args); if (!strcmp (pathname, "/dev/dsp")) { if (!getenv ("ESPEAKER")) { int ret; flags |= O_NONBLOCK; if ((ret = (*func) (pathname, flags, mode)) >= 0) return ret; } DPRINTF ("hijacking /dev/dsp open, and taking it to esd...\n"); settings = done = 0; return (sndfd = esd_open_sound (NULL)); } else if (use_mixer && !strcmp (pathname, "/dev/mixer")) { DPRINTF ("hijacking /dev/mixer open, and taking it to esd...\n"); return (mixfd = (*func) (mixer, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)); } else return (*func) (pathname, flags, mode); } static int dspctl (int fd, request_t request, void *argp) { static esd_format_t fmt = ESD_STREAM | ESD_PLAY | ESD_MONO; static int speed; int *arg = (int *) argp; DPRINTF ("hijacking /dev/dsp ioctl, and sending it to esd " "(%d : %x - %p)\n", fd, request, argp); switch (request) { case SNDCTL_DSP_SETFMT: fmt |= (*arg & 0x30) ? ESD_BITS16 : ESD_BITS8; settings |= 1; break; case SNDCTL_DSP_SPEED: speed = *arg; settings |= 2; break; case SNDCTL_DSP_STEREO: fmt &= ~ESD_MONO; fmt |= (*arg) ? ESD_STEREO : ESD_MONO; break; case SNDCTL_DSP_GETBLKSIZE: *arg = ESD_BUF_SIZE; break; case SNDCTL_DSP_GETFMTS: *arg = 0x38; break; case SNDCTL_DSP_GETCAPS: *arg = 0; break; case SNDCTL_DSP_GETOSPACE: { audio_buf_info *bufinfo = (audio_buf_info *) argp; bufinfo->bytes = 100000; } break; default: DPRINTF ("unhandled /dev/dsp ioctl (%x - %p)\n", request, argp); break; } if (settings == 3 && !done) { int proto = ESD_PROTO_STREAM_PLAY; done = 1; if (write (sndfd, &proto, sizeof (proto)) != sizeof (proto)) return -1; if (write (sndfd, &fmt, sizeof (fmt)) != sizeof (fmt)) return -1; if (write (sndfd, &speed, sizeof (speed)) != sizeof (speed)) return -1; if (write (sndfd, ident, ESD_NAME_MAX) != ESD_NAME_MAX) return -1; fmt = ESD_STREAM | ESD_PLAY | ESD_MONO; speed = 0; if (use_mixer) { int esd = -1, player = -1; int left, right; while (player < 0) mix_init (&esd, &player); get_volume (&left, &right); DPRINTF ("panning %d - %d %d\n", player, left, right); esd_set_stream_pan (esd, player, left, right); } } return 0; } int mixctl (int fd, request_t request, void *argp) { static int esd = -1, player = -1; static int left, right; int *arg = (int *) argp; DPRINTF ("hijacking /dev/mixer ioctl, and sending it to esd " "(%d : %x - %p)\n", fd, request, argp); switch (request) { case SOUND_MIXER_READ_DEVMASK: *arg = 5113; break; case SOUND_MIXER_READ_PCM: mix_init (&esd, &player); if (player > 0) { esd_info_t *all_info; esd_player_info_t *player_info; if (all_info = esd_get_all_info (esd)) { for (player_info = all_info->player_list; player_info; player_info = player_info->next) if (player_info->source_id == player) { *arg = ESD_VOL_TO_OSS (player_info->left_vol_scale, player_info->right_vol_scale); } esd_free_all_info (all_info); } else return -1; } else { get_volume (&left, &right); *arg = ESD_VOL_TO_OSS (left, right); } break; case SOUND_MIXER_WRITE_PCM: mix_init (&esd, &player); left = OSS_VOL_TO_ESD_LEFT (*arg); right = OSS_VOL_TO_ESD_RIGHT (*arg); set_volume (left, right); if (player > 0) { DPRINTF ("panning %d - %d %d\n", player, left, right); esd_set_stream_pan (esd, player, left, right); } break; default: DPRINTF ("unhandled /dev/mixer ioctl (%x - %p)\n", request, argp); break; } return 0; } int ioctl (int fd, request_t request, ...) { static int (*func) (int, request_t, void *) = NULL; va_list args; void *argp; if (!func) func = (int (*) (int, request_t, void *)) dlsym (REAL_LIBC, "ioctl"); va_start (args, request); argp = va_arg (args, void *); va_end (args); if (fd != sndfd && fd != mixfd) return (*func) (fd, request, argp); else if (fd == sndfd) return dspctl (fd, request, argp); else if (use_mixer) return mixctl (fd, request, argp); } int close (int fd) { static int (*func) (int) = NULL; if (!func) func = (int (*) (int)) dlsym (REAL_LIBC, "close"); if (fd == sndfd) sndfd = -1; else if (fd == mixfd) mixfd = -1; return (*func) (fd); } #ifdef MULTIPLE_X11AMP #include <socketbits.h> #include <sys/param.h> #include <sys/un.h> #define ENVSET "X11AMPNUM" int unlink (const char *filename) { static int (*func) (const char *) = NULL; char *num; if (!func) func = (int (*) (const char *)) dlsym (REAL_LIBC, "unlink"); if (!strcmp (filename, "/tmp/X11Amp_CTRL") && (num = getenv (ENVSET))) { char buf[PATH_MAX] = "/tmp/X11Amp_CTRL"; strcat (buf, num); return (*func) (buf); } else return (*func) (filename); } typedef int (*sa_func_t) (int, struct sockaddr *, int); static int sockaddr_mangle (sa_func_t func, int fd, struct sockaddr *addr, int len) { char *num; if (!strcmp (((struct sockaddr_un *) addr)->sun_path, "/tmp/X11Amp_CTRL") && (num = getenv(ENVSET))) { int ret; char buf[PATH_MAX] = "/tmp/X11Amp_CTRL"; struct sockaddr_un *new_addr = malloc (len); strcat (buf, num); memcpy (new_addr, addr, len); strcpy (new_addr->sun_path, buf); ret = (*func) (fd, (struct sockaddr *) new_addr, len); free (new_addr); return ret; } else return (*func) (fd, addr, len); } int bind (int fd, struct sockaddr *addr, int len) { static sa_func_t func = NULL; if (!func) func = (sa_func_t) dlsym (REAL_LIBC, "bind"); return sockaddr_mangle (func, fd, addr, len); } int connect (int fd, struct sockaddr *addr, int len) { static sa_func_t func = NULL; if (!func) func = (sa_func_t) dlsym (REAL_LIBC, "connect"); return sockaddr_mangle (func, fd, addr, len); } #endif /* MULTIPLE_X11AMP */ #else /* __GNUC__ */ void nogcc (void) { ident = NULL; } #endif /* __GNUC__ */