/* -*-C-*-

$Id: ossctl.c,v 1.1 2001/04/10 05:35:55 cph Exp $

Copyright (C) 2001 Hewlett-Packard Company

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program 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
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <glib.h>

#include <errno.h>
#include <linux/soundcard.h>
#include <sys/ioctl.h>
#include <sys/types.h>

typedef struct device_info_s
{
  GArray * channels;
} device_info_t;

typedef struct channel_info_s
{
  unsigned char index;
  unsigned short volume;
  unsigned int is_recorder : 1;
} channel_info_t;

static const char * line_format = "%c %.02x %.04x\n";

static void usage (const char **);
static void write_configuration (GArray *);
static GArray * read_configuration (void);
static const char * device_name (unsigned int);
static GArray * read_from_devices (void);
static int read_from_device (int, unsigned int, device_info_t *);
static void write_to_devices (GArray *);

int
main (int argc, const char ** argv)
{
  if (argc != 2)
    usage (argv);
  if ((strcmp ((argv[1]), "save")) == 0)
    write_configuration (read_from_devices ());
  else if ((strcmp ((argv[1]), "restore")) == 0)
    write_to_devices (read_configuration ());
  else
    usage (argv);
  return (0);
}

static void
usage (const char ** argv)
{
  fprintf (stderr, "usage: %s (save|restore)\n", (argv[0]));
  fflush (stderr);
  exit (1);
}

static void
write_configuration (GArray * devices)
{
  unsigned int n_devices = (devices -> len);
  unsigned int i;

  for (i = 0; (i < n_devices); i += 1)
    {
      device_info_t * d = (& (g_array_index (devices, device_info_t, i)));
      unsigned int n_channels = ((d -> channels) -> len);
      unsigned int j;

      printf (line_format, 'D', i, 0);
      for (j = 0; (j < n_channels); j += 1)
	{
	  channel_info_t * c
	    = (& (g_array_index ((d -> channels), channel_info_t, j)));
	  printf (line_format,
		  ((c -> is_recorder) ? 'R' : 'C'),
		  (c -> index),
		  (c -> volume));
	}
      printf (line_format, 'X', 0, 0);
    }
}

static GArray *
read_configuration (void)
{
  GArray * devices = (g_array_new (0, 0, (sizeof (device_info_t))));
  device_info_t d;
  int starting = 1;

  while (1)
    {
      unsigned int index;
      unsigned int volume;
      char type;
      int n_matched = (scanf ("%c %x %x\n", (&type), (&index), (&volume)));
      if (n_matched != 3)
	g_error ("Malformed input stream (n_matched == %d).", n_matched);
      switch (type)
	{
	case 'D':
	  if (starting)
	    starting = 0;
	  else
	    devices = (g_array_append_val (devices, d));
	  (d . channels) = (g_array_new (0, 0, (sizeof (channel_info_t))));
	  break;
	case 'X':
	  if (!starting)
	    devices = (g_array_append_val (devices, d));
	  return (devices);
	case 'C':
	case 'R':
	  {
	    channel_info_t c;
	    (c . index) = index;
	    (c . volume) = volume;
	    (c . is_recorder) = (type == 'R');
	    (d . channels) = (g_array_append_val ((d . channels), c));
	  }
	  break;
	default:
	  g_error ("Malformed input stream.");
	  break;
	}
    }
}

static const char *
device_name (unsigned int i)
{
  static char name [32];
  if (i == 0)
    sprintf (name, "/dev/mixer");
  else
    sprintf (name, "/dev/mixer%u", i);
  return (name);
}

static GArray *
read_from_devices (void)
{
  GArray * devices = (g_array_new (0, 0, (sizeof (device_info_t))));
  int i = 0;
  while (1)
    {
      const char * filename = (device_name (i));
      int fd = (open (filename, O_RDONLY, 0));
      device_info_t device;

      if (fd < 0)
	break;
      (device . channels) = (g_array_new (0, 0, (sizeof (channel_info_t))));
      if (!read_from_device (fd, (i + 1), (&device)))
	{
	  close (fd);
	  g_array_free ((device . channels), 1);
	  break;
	}
      close (fd);
      devices = (g_array_append_val (devices, device));
      i += 1;
    }
  return (devices);
}

static int
read_from_device (int fd, unsigned int device_number, device_info_t * d)
{
  {
    int sound_version;
    if (((ioctl (fd, OSS_GETVERSION, (&sound_version))) == 0)
	&& (sound_version != SOUND_VERSION))
      g_warning
	("Mismatch between OSS version of program and kernel.\n"
	 "The program was compiled with version 0x%.06x,\n"
	 "but the kernel implements version 0x%.06x.",
	 SOUND_VERSION, sound_version);
  }
  {
    int implemented_channels;
    int recorders;
    int stereo;
    unsigned int index;

    if (((ioctl (fd, SOUND_MIXER_READ_DEVMASK, (&implemented_channels)))
	 || (ioctl (fd, SOUND_MIXER_READ_RECSRC, (&recorders)))
	 || (ioctl (fd, SOUND_MIXER_READ_STEREODEVS, (&stereo))))
	< 0)
      {
	if (errno == ENXIO)
	  return (0);
	g_error ("Unable to read mixer bitmasks for card %u: %s",
		 device_number, (g_strerror (errno)));
      }
    for (index = 0; (index < SOUND_MIXER_NRDEVICES); index += 1)
      if ((implemented_channels & (1 << index)) != 0)
	{
	  channel_info_t c;
	  int volume;

	  (c . index) = index;
	  (c . is_recorder) = ((recorders & (1 << index)) != 0);
	  if ((ioctl (fd, (MIXER_READ (index)), (&volume))) < 0)
	    g_error ("Unable to read mixer volume for channel %u[%u]: %s",
		     device_number, index, (g_strerror (errno)));
	  (c . volume)
	    = (volume & (((stereo & (1 << index)) != 0) ? 0xffff : 0x0ff));
	  (d -> channels) = (g_array_append_val ((d -> channels), c));
	}
  }
  return (1);
}

static void
write_to_devices (GArray * devices)
{
  unsigned int n_devices = (devices -> len);
  unsigned int i;

  for (i = 0; (i < n_devices); i += 1)
    {
      device_info_t * d = (& (g_array_index (devices, device_info_t, i)));
      unsigned int n_channels = ((d -> channels) -> len);
      int recorders = 0;
      int fd;
      unsigned int j;

      {
	const char * filename = (device_name (i));
	filename = (device_name (i));
	fd = (open (filename, O_WRONLY, 0));
	if (fd < 0)
	  g_error ("Unable to open device %s: %s",
		   filename, (g_strerror (errno)));
      }
      for (j = 0; (j < n_channels); j += 1)
	{
	  channel_info_t * c
	    = (& (g_array_index ((d -> channels), channel_info_t, j)));
	  int volume = (c -> volume);
	  if ((ioctl (fd, (MIXER_WRITE (c -> index)), (&volume))) < 0)
	    g_error ("Unable to set volume: %s", (g_strerror (errno)));
	  if (c -> is_recorder)
	    recorders |= (1 << (c -> index));
	}
      if ((ioctl (fd, SOUND_MIXER_WRITE_RECSRC, (&recorders))) < 0)
	g_error ("Unable to select recording device: %s",
		 (g_strerror (errno)));
      close (fd);
    }
}
