/*======================================================================
  xmix: An X11 interface for Linux derived from:
----------
   xmix: X interface to the Sound Blaster mixer.
   [ This file is a part of SBlast-BSD-1.5 ]

   Steve Haehnichen <shaehnic@ucsd.edu>
 
   xmix.c,v 1.5 1992/09/14 03:17:21 steve Exp

   Copyright (C) 1992 Steve Haehnichen.

   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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.

 * xmix.c,v
 * Revision 1.5  1992/09/14  03:17:21  steve
 * Released with driver v1.5.
 * Converted over to stereo CD levels.
 *
 * Revision 1.4  1992/06/12  22:33:39  steve
 * Much better.
 * Moved many resources into source code.
 * Ready for release in v1.4
 *
 * Revision 1.3  1992/06/08  04:36:26  steve
 * Works fine.
 *
----------
Linux/Sound driver V2.0 port by Hal Brand (brand@netcom.com or brand1@llnl.gov)
Let's call this xmix V2.0 since it is significantly changed.

Patches for using the sound driver capabilities from:
  <rick@digibd.com>
I added a little bit of code to have X11 disable the controls but BIG THANKS
to Rick! I'll call this version V2.1

======================================================================*/

#include <stdio.h>
#include <sys/errno.h>
#include <fcntl.h>

/*
 * Include files required for all Toolkit programs
 */
#include <X11/Intrinsic.h>	/* Intrinsics Definitions */
#include <X11/StringDefs.h>	/* Standard Name-String definitions */
#include <X11/Shell.h>		/* Shell Definitions */

/*
 * Public include file for widgets we actually use in this file.
 */
#include <X11/Xaw/Command.h>	/* Athena Command Widget */
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/Label.h>

#include "xmix.icon.bit"	/* The icon, of course */
#include "locked.bit"		/* L & R locked together (From dirt) */
#include "unlocked.bit"		/* L & R free to be different (From dirt)*/
#include "circle_on.bit"
#include "circle_off.bit"
#include "square_empty.bit"
#include "square_with_x.bit"

#include <linux/soundcard.h>

#define SOUND_FULL_SCALE 100.0
#define MAX_SOUND_VOL 95
#define MIN_SOUND_VOL 05
#define SLIDER_LENGTH (0.05)



/*
 * Here's the debugging macro I use here and there, so I can turn
 * them all on or off in one place.
 */
/* #define DEBUG */
#ifdef DEBUG
#define DPRINTF(x)      printf x
#else
#define DPRINTF(x)
#endif


/*
 * Some of these Xt names are too long for nice formatting
 */
#define MW 		XtVaCreateManagedWidget
#define PORTION(val)	(1.0 - (float)val / SOUND_FULL_SCALE)
#define MK_BITMAP(bits, width, height) \
  XCreateBitmapFromData (XtDisplay (topLevel), \
			 RootWindowOfScreen (XtScreen (topLevel)),\
			 bits, width, height)
#define SCROLLBAR_RES	 XtNwidth, 23, XtNheight, 100
#define CENTER(widg)	 XtVaSetValues (widg, XtNwidth,\
					centering_width, NULL)


typedef struct stereovolume
{
  unsigned char left;
  unsigned char right;
} StereoVolume;


typedef struct volctrl
{
  int mixer_id;
  Widget formw;
  Widget labelw;
  Widget leftw;
  Widget rightw;
  Widget lockw;
  int locked;
  StereoVolume volume;
  int supported;
} VolumeControl;



/*
 * I'm lazy.  Everything is global today.
 */
Pixmap icon_pixmap, locked_pixmap, unlocked_pixmap;
Pixmap circle_on_pixmap, circle_off_pixmap;
Pixmap square_empty_pixmap, square_with_x_pixmap;
Widget topLevel, quit, whole, buttons, sliders;
Widget sources, line_src, mic_src, cd_src, source_label;
Widget line_src_label, mic_src_label, cd_src_label;
int centering_width;
int mixer_fd;

VolumeControl master, line, dsp, fm, cd, mic, bass, treble, reclvl;



/*
 * Convert all the pixmap data into Pixmap objects.
 */
static void install_pixmaps (void)
{
  /* The icon bitmap */
  icon_pixmap = MK_BITMAP (xmix_bits, xmix_width, xmix_height);
  XtVaSetValues (topLevel, XtNiconPixmap, icon_pixmap, NULL);

  locked_pixmap = MK_BITMAP (locked_bits, locked_width, locked_height);
  unlocked_pixmap = MK_BITMAP (unlocked_bits, unlocked_width, unlocked_height);
  circle_on_pixmap =
    MK_BITMAP (circle_on_bits, circle_on_width, circle_on_height);
  circle_off_pixmap =
    MK_BITMAP (circle_off_bits, circle_off_width, circle_off_height);
  square_empty_pixmap =
    MK_BITMAP (square_empty_bits, square_empty_width, square_empty_height);
  square_with_x_pixmap =
    MK_BITMAP (square_with_x_bits, square_with_x_width, square_with_x_height);
}



static void sync_slider(VolumeControl *vcptr)
{
  float portion;

  if (!vcptr->supported)
    return;
  if (ioctl(mixer_fd,MIXER_READ(vcptr->mixer_id),&vcptr->volume) == -1)
    perror("Error reading volumes in sync_slider");
  portion = PORTION(vcptr->volume.left);
  XawScrollbarSetThumb (vcptr->leftw, portion, SLIDER_LENGTH);
  if (vcptr->rightw != NULL)
    {
      portion = PORTION(vcptr->volume.right);
      XawScrollbarSetThumb (vcptr->rightw, portion, SLIDER_LENGTH);
    }
  return;
}




static void sync_lock(VolumeControl *vcptr)
{
  if (vcptr->locked)
      XtVaSetValues (vcptr->lockw, XtNbitmap, locked_pixmap, NULL);
  else
      XtVaSetValues (vcptr->lockw, XtNbitmap, unlocked_pixmap, NULL);
  CENTER (vcptr->lockw);
}



static void Handle_lock(Widget w, XtPointer client_data, XtPointer call_data)
{
  VolumeControl *vcptr = client_data;

  vcptr->locked = !(vcptr->locked);
  if (vcptr->locked && vcptr->volume.left != vcptr->volume.right)
    {
      vcptr->volume.left = vcptr->volume.right =
	((vcptr->volume.left + vcptr->volume.right)/2);
      if (ioctl(mixer_fd,MIXER_WRITE(vcptr->mixer_id),&vcptr->volume) == -1)
	perror("Error in Handle_lock writing volumes");
      sync_slider(vcptr);
    }
  sync_lock(vcptr);
}



/*
 * Match the shown button to the mixer's idea of the recording source.
 */
static void sync_source_display(void)
{
  int source,id;
  
  if (ioctl(mixer_fd,SOUND_MIXER_READ_RECSRC,&source) == -1)
    perror("Error reading mixer recording source");
  DPRINTF(("Current recording source is %d\n",source));
  id = 0;
  if (source == 0)
    {
      id = SOUND_MIXER_MIC;
      source = 1 << id;
      if (ioctl(mixer_fd,SOUND_MIXER_WRITE_RECSRC,&source) == -1)
	perror("Error setting mixer recording source to MIC!");
    }
  else
    {
      while ((source & 1) == 0)
	{
	  source = source>>1;
	  ++id;
	}
    }
  DPRINTF(("Current recording source ID=%d\n",id));
  switch (id)
    {
    case SOUND_MIXER_MIC:
      XtVaSetValues (mic_src, XtNbitmap, circle_on_pixmap, NULL);
      XtVaSetValues (cd_src, XtNbitmap, circle_off_pixmap, NULL);
      XtVaSetValues (line_src, XtNbitmap, circle_off_pixmap, NULL);
      break;
    case SOUND_MIXER_CD:
      XtVaSetValues (mic_src, XtNbitmap, circle_off_pixmap, NULL);
      XtVaSetValues (cd_src, XtNbitmap, circle_on_pixmap, NULL);
      XtVaSetValues (line_src, XtNbitmap, circle_off_pixmap, NULL);
      break;
    case SOUND_MIXER_LINE:
      XtVaSetValues (mic_src, XtNbitmap, circle_off_pixmap, NULL);
      XtVaSetValues (cd_src, XtNbitmap, circle_off_pixmap, NULL);
      XtVaSetValues (line_src, XtNbitmap, circle_on_pixmap, NULL);
      break;
    default:
      fprintf (stderr, "Invalid recording source!\n");
    }
  return;
}




/*
 * Callback for selecting a new source.
 * Basically, we set the mixer to the new source, and then
 * let sync_source_display handle the visual feedback.
 * (Easier than radioGroups.)
 */
static void Handle_source(Widget w, XtPointer client_data,
			  XtPointer call_data)
{
  int source = 1 << ((int) client_data);

  DPRINTF(("Setting recording source to %d (%d)\n",source,(int)client_data));
  if (ioctl(mixer_fd,SOUND_MIXER_WRITE_RECSRC,&source) == -1)
    perror("Error writing mixer recording source");

  sync_source_display();
}




/*
 * Quit button callback function
 */
void Quit(Widget w, XtPointer client_data, XtPointer call_data)
{
  DPRINTF (("Exiting...\n"));
  exit (0);
}



static void set_slider(VolumeControl *vcptr, Widget w, int value)
{
  float portion = PORTION(value);
  int update_needed;

  DPRINTF (("set_slider: val = %d, portion = %f\n",value,portion));

  if (vcptr->locked)
    {
      update_needed = (vcptr->volume.left != value);
      DPRINTF (("set_slider: locked; updating\n"));
      vcptr->volume.left = vcptr->volume.right = value;
      XawScrollbarSetThumb(vcptr->leftw, portion, SLIDER_LENGTH);
      XawScrollbarSetThumb(vcptr->rightw, portion, SLIDER_LENGTH);
    }
  else
    {
      if (vcptr->leftw == w)
	{
	  update_needed = (vcptr->volume.left != value);
	  vcptr->volume.left = value;
	}
      else
	{
	  update_needed = (vcptr->volume.right != value);
	  vcptr->volume.right = value;
	}
      XawScrollbarSetThumb(w, portion, SLIDER_LENGTH);
    }

  /*
   * If the new value is at a different notch than the current setting,
   * then inform the mixer, and adopt the setting.
   * Otherwise, don't waste time setting the mixer.
   */
  if (update_needed)
    {
      DPRINTF (("set_slider: updating mixer\n"));
      if (vcptr->supported
        && ioctl(mixer_fd,MIXER_WRITE(vcptr->mixer_id),&vcptr->volume) == -1)
	perror("Error writing mixer in Handle_slider");
    }
  return;
}



/*
 * This is for pushing a slider to MAX or MIN position.
 * Of questionable utility, yes..
 */
static void Handle_slam_slider(Widget w, XtPointer client_data,
			       XtPointer call_data)
{
  int val;
  VolumeControl *vcptr = client_data;

  val = (vcptr->leftw == w) ? vcptr->volume.left : vcptr->volume.right;
  if ((int)call_data < 0)
    {
      if (val >= MAX_SOUND_VOL)
	val = 10*((MAX_SOUND_VOL-1)/10);
      else
	val -= 10;
      if (val < MIN_SOUND_VOL) val = MIN_SOUND_VOL;
    }
  else
    {
      if (val <= MIN_SOUND_VOL)
	val = 10*((MIN_SOUND_VOL+9)/10);
      else
	val += 10;
      if (val > MAX_SOUND_VOL) val = MAX_SOUND_VOL;
    }

  set_slider(vcptr,w,val);
  return;
}



/*
 * XtNjumpProc callback for volume fader scrollbar widgets.
 * Great pains are taken to make the slider accurately reflect
 * the granular mixer setting, without actually querying the mixer
 * device.  (This is faster.)
 */
static void Handle_slider(Widget w, XtPointer client_data,
			  XtPointer call_data)
{
  int val;
  VolumeControl *vcptr = client_data;

  DPRINTF (("Handle_slider got %f\n", *(float*)call_data));
  val = (int)(.5 + (SOUND_FULL_SCALE * (1.0 - *(float*)call_data)));
  if (val > MAX_SOUND_VOL)
    val = MAX_SOUND_VOL;
  else if (val < MIN_SOUND_VOL)
    val = MIN_SOUND_VOL;
  set_slider(vcptr,w,val);
  return;
}



static void sync_sliders(void)
{
  sync_slider(&master);
  sync_slider(&bass);
  sync_slider(&treble);
  sync_slider(&line);
  sync_slider(&dsp);
  sync_slider(&fm);
  sync_slider(&cd);
  sync_slider(&mic);
  sync_slider(&reclvl);
}




/*
 * Rescan the mixer settings and make all the indicators reflect
 * the current values.
 */
static void sync_display(void)
{
  DPRINTF(("Updating..\n"));
  sync_source_display();
  sync_sliders();
}



static void set_supported(VolumeControl *vcptr, int is_sup)
{
  vcptr->supported = is_sup;
  XtSetSensitive(vcptr->formw,is_sup);
  return;
}


void main (int argc, char **argv)
{
  XtAppContext app_context;
  int scroll_sep, longway;
  Widget version;
  int supported;

  longway = (argc > 1 && strcmp(argv[1],"-l") == 0);

  topLevel = XtVaAppInitialize (&app_context,
     "XMix",			/* Application class */
     NULL, 0,			/* command line option list */
     &argc, argv,		/* command line args */
     NULL,			/* for missing app-defaults file */
     NULL);			/* terminate varargs list */

  whole = MW ("whole", formWidgetClass, topLevel, NULL);
  
  sliders = MW ("sliders", formWidgetClass, whole, NULL);
  
  master.mixer_id = SOUND_MIXER_VOLUME;
  master.formw = MW ("master_form", formWidgetClass, sliders, NULL);
  master.labelw =  MW ("master_label", labelWidgetClass, master.formw,
		       XtNlabel, "Master", NULL);
  master.leftw = MW ("master_l", scrollbarWidgetClass, master.formw,
		     SCROLLBAR_RES, XtNfromVert, master.labelw,NULL);
  master.rightw = MW ("master_r", scrollbarWidgetClass, master.formw,
		      SCROLLBAR_RES, XtNfromHoriz, master.leftw,
		      XtNfromVert, master.labelw, NULL);
  master.lockw = MW ("master_lock", commandWidgetClass, master.formw,
		     XtNfromVert, master.leftw, NULL);

  bass.mixer_id = SOUND_MIXER_BASS;
  bass.formw = MW ("bass_form", formWidgetClass, sliders,
		   XtNfromHoriz, master.formw, NULL);
  bass.labelw =  MW ("bass_label", labelWidgetClass, bass.formw,
		     XtNlabel, "Bass", NULL);
  bass.leftw = MW ("bass_l", scrollbarWidgetClass, bass.formw, SCROLLBAR_RES,
		   XtNfromVert, bass.labelw, NULL);
  bass.rightw = MW ("bass_r", scrollbarWidgetClass, bass.formw, SCROLLBAR_RES,
		    XtNfromHoriz, bass.leftw, XtNfromVert, bass.labelw, NULL);
  bass.lockw = MW ("bass_lock", commandWidgetClass, bass.formw,
		   XtNfromVert, bass.leftw, NULL);

  treble.mixer_id = SOUND_MIXER_TREBLE;
  treble.formw = MW ("treble_form", formWidgetClass, sliders,
		     XtNfromHoriz, bass.formw, NULL);
  treble.labelw =  MW ("treble_label", labelWidgetClass, treble.formw,
		       XtNlabel, "Treble", NULL);
  treble.leftw = MW ("treble_l", scrollbarWidgetClass, treble.formw,
		     SCROLLBAR_RES, XtNfromVert, treble.labelw, NULL);
  treble.rightw = MW ("treble_r", scrollbarWidgetClass, treble.formw,
		      SCROLLBAR_RES, XtNfromHoriz, treble.leftw,
		      XtNfromVert, treble.labelw, NULL);
  treble.lockw = MW ("treble_lock", commandWidgetClass, treble.formw,
		     XtNfromVert, treble.leftw, NULL);

  line.mixer_id = SOUND_MIXER_LINE;
  line.formw = MW ("line_form", formWidgetClass, sliders,
		   XtNfromHoriz, treble.formw, NULL);
  line.labelw =  MW ("line_label", labelWidgetClass, line.formw,
		     XtNlabel, "Line", NULL);
  line.leftw = MW ("line_l", scrollbarWidgetClass, line.formw, SCROLLBAR_RES,
		   XtNfromVert, line.labelw, NULL);
  line.rightw = MW ("line_r", scrollbarWidgetClass, line.formw, SCROLLBAR_RES,
		    XtNfromHoriz, line.leftw, XtNfromVert, line.labelw, NULL);
  line.lockw = MW ("line_lock", commandWidgetClass, line.formw,
		   XtNfromVert, line.leftw, NULL);

  dsp.mixer_id = SOUND_MIXER_PCM;
  dsp.formw = MW ("dsp_form", formWidgetClass, sliders,
		  longway ? XtNfromHoriz : XtNfromVert,
		  longway ? line.formw : master.formw,
		  NULL);
  dsp.labelw =  MW ("dsp_label", labelWidgetClass, dsp.formw,
		   XtNlabel, "DSP", NULL);
  dsp.leftw = MW ("dsp_l", scrollbarWidgetClass, dsp.formw, SCROLLBAR_RES,
	      XtNfromVert, dsp.labelw, NULL);
  dsp.rightw = MW ("dsp_r", scrollbarWidgetClass, dsp.formw, SCROLLBAR_RES,
	      XtNfromVert, dsp.labelw,
	      XtNfromHoriz, dsp.leftw, NULL);
  dsp.lockw = MW ("dsp_lock", commandWidgetClass, dsp.formw,
		 XtNfromVert, dsp.leftw, NULL);

  fm.mixer_id = SOUND_MIXER_SYNTH;
  fm.formw = MW ("fm_form", formWidgetClass, sliders,
		 XtNfromHoriz, dsp.formw,
		 longway ? NULL : XtNfromVert, bass.formw, NULL);
  fm.labelw =  MW ("fm_label", labelWidgetClass, fm.formw,
		  XtNlabel, "FM", NULL);
  fm.leftw = MW ("fm_l", scrollbarWidgetClass, fm.formw, SCROLLBAR_RES,
	     XtNfromVert, fm.labelw, NULL);
  fm.rightw = MW ("fm_r", scrollbarWidgetClass, fm.formw, SCROLLBAR_RES,
	     XtNfromVert, fm.labelw,
	     XtNfromHoriz, fm.leftw, NULL);
  fm.lockw = MW ("fm_lock", commandWidgetClass, fm.formw,
		XtNfromVert, fm.leftw, NULL);

  cd.mixer_id = SOUND_MIXER_CD;
  cd.formw = MW ("cd_form", formWidgetClass, sliders,
		 XtNfromHoriz, fm.formw,
		 longway ? NULL : XtNfromVert, treble.formw, NULL);
  cd.labelw =  MW ("cd_label", labelWidgetClass, cd.formw,
		  XtNlabel, "CD", NULL);
  cd.leftw = MW ("cd_l", scrollbarWidgetClass, cd.formw, SCROLLBAR_RES,
	     XtNfromVert, cd.labelw, NULL);
  cd.rightw = MW ("cd_r", scrollbarWidgetClass, cd.formw, SCROLLBAR_RES,
	     XtNfromVert, cd.labelw, XtNfromHoriz, cd.leftw, NULL);
  cd.lockw = MW ("cd_lock", commandWidgetClass, cd.formw,
		XtNfromVert, cd.leftw, NULL);

  mic.mixer_id = SOUND_MIXER_MIC;
  mic.formw = MW ("mic_form", formWidgetClass, sliders,
		  XtNfromHoriz, cd.formw,
		  longway ? NULL : XtNfromVert, line.formw, NULL);
  mic.labelw =  MW ("mic_label", labelWidgetClass, mic.formw,
		   XtNlabel, "Mic", NULL);
  mic.leftw = MW ("mic_level", scrollbarWidgetClass, mic.formw, SCROLLBAR_RES,
		  XtNfromVert, mic.labelw, NULL);
  mic.rightw = NULL;
  mic.lockw = NULL;

  reclvl.mixer_id = SOUND_MIXER_RECLEV;
  reclvl.formw = MW ("reclvl_form", formWidgetClass, sliders,
		     XtNfromHoriz, mic.formw,
		     longway ? NULL : XtNfromVert, line.formw, NULL);
  reclvl.labelw =  MW ("reclvl_label", labelWidgetClass, reclvl.formw,
		       XtNlabel, "Reclvl", NULL);
  reclvl.leftw = MW ("reclvl_level", scrollbarWidgetClass, reclvl.formw,
		     SCROLLBAR_RES, XtNfromVert, reclvl.labelw, NULL);
  reclvl.rightw = NULL;
  reclvl.lockw = NULL;

  buttons = MW ("buttons", formWidgetClass, whole,
		XtNfromHoriz, sliders, NULL);


  version = MW ("Version", labelWidgetClass, buttons,
		XtNlabel, "XMix V2.1", NULL);

  sources = MW ("sources", formWidgetClass, buttons,
		XtNfromVert, version, NULL);
  source_label = MW ("source_label", labelWidgetClass, sources,
		     XtNlabel, "Recording Source", NULL);
  line_src = MW ("line_src", commandWidgetClass, sources,
		 XtNfromVert, source_label, NULL);
  line_src_label = MW ("line_src_label", labelWidgetClass, sources,
		       XtNlabel, "Line In",
		       XtNfromVert, source_label,
		       XtNfromHoriz, line_src, NULL);
  mic_src = MW ("mic_src", commandWidgetClass, sources,
		XtNfromVert, line_src, NULL);
  mic_src_label = MW ("mic_src_label", labelWidgetClass, sources,
		      XtNlabel, "Microphone",
		      XtNfromVert, line_src,
		      XtNfromHoriz, mic_src, NULL);
  cd_src = MW ("cd_src", commandWidgetClass, sources,
	       XtNfromVert, mic_src, NULL);
  cd_src_label = MW ("cd_src_label", labelWidgetClass, sources,
		     XtNlabel, "CD",
		     XtNfromVert, mic_src,
		     XtNfromHoriz, cd_src, NULL);
  quit = MW ("quit", commandWidgetClass, buttons,
	     XtNfromVert, sources,
	     XtNlabel, "Quit",
	     XtNwidth, 50,
	     XtNheight, 20,
	     NULL);


  XtVaGetValues (mic.leftw, XtNwidth, &centering_width, NULL);
  CENTER (mic.labelw);
  XtVaGetValues (reclvl.leftw, XtNwidth, &centering_width, NULL);
  CENTER (reclvl.labelw);
  XtVaGetValues (master.formw, XtNhorizDistance, &scroll_sep, NULL);
  XtVaGetValues (master.leftw, XtNwidth, &centering_width, NULL);
  centering_width = centering_width * 2 + scroll_sep;
  CENTER (master.labelw);
  CENTER (bass.labelw);
  CENTER (treble.labelw);
  CENTER (line.labelw);
  CENTER (dsp.labelw);
  CENTER (fm.labelw);
  CENTER (cd.labelw);

  XtAddEventHandler (topLevel, EnterWindowMask, FALSE,
		     (XtEventHandler) sync_display, NULL);
  XtAddCallback (quit, XtNcallback, Quit, 0 /* client_data */ );
  XtAddCallback (master.leftw, XtNjumpProc, Handle_slider, &master);
  XtAddCallback (master.leftw, XtNscrollProc, Handle_slam_slider, &master);
  XtAddCallback (master.rightw, XtNjumpProc, Handle_slider, &master);
  XtAddCallback (master.rightw, XtNscrollProc, Handle_slam_slider, &master);
  XtAddCallback (master.lockw, XtNcallback, Handle_lock, &master);
  XtAddCallback (bass.leftw, XtNjumpProc, Handle_slider, &bass);
  XtAddCallback (bass.leftw, XtNscrollProc, Handle_slam_slider, &bass);
  XtAddCallback (bass.rightw, XtNjumpProc, Handle_slider, &bass);
  XtAddCallback (bass.rightw, XtNscrollProc, Handle_slam_slider, &bass);
  XtAddCallback (bass.lockw, XtNcallback, Handle_lock, &bass);
  XtAddCallback (treble.leftw, XtNjumpProc, Handle_slider, &treble);
  XtAddCallback (treble.leftw, XtNscrollProc, Handle_slam_slider, &treble);
  XtAddCallback (treble.rightw, XtNjumpProc, Handle_slider, &treble);
  XtAddCallback (treble.rightw, XtNscrollProc, Handle_slam_slider, &treble);
  XtAddCallback (treble.lockw, XtNcallback, Handle_lock, &treble);
  XtAddCallback (line.leftw, XtNjumpProc, Handle_slider, &line);
  XtAddCallback (line.leftw, XtNscrollProc, Handle_slam_slider, &line);
  XtAddCallback (line.rightw, XtNjumpProc, Handle_slider, &line);
  XtAddCallback (line.rightw, XtNscrollProc, Handle_slam_slider, &line);
  XtAddCallback (line.lockw, XtNcallback, Handle_lock, &line);
  XtAddCallback (dsp.leftw, XtNjumpProc, Handle_slider, &dsp);
  XtAddCallback (dsp.leftw, XtNscrollProc, Handle_slam_slider, &dsp);
  XtAddCallback (dsp.rightw, XtNjumpProc, Handle_slider, &dsp);
  XtAddCallback (dsp.rightw, XtNscrollProc, Handle_slam_slider, &dsp);
  XtAddCallback (dsp.lockw, XtNcallback, Handle_lock, &dsp);
  XtAddCallback (fm.leftw, XtNjumpProc, Handle_slider, &fm);
  XtAddCallback (fm.leftw, XtNscrollProc, Handle_slam_slider, &fm);
  XtAddCallback (fm.rightw, XtNjumpProc, Handle_slider, &fm);
  XtAddCallback (fm.rightw, XtNscrollProc, Handle_slam_slider, &fm);
  XtAddCallback (fm.lockw, XtNcallback, Handle_lock, &fm);
  XtAddCallback (cd.leftw, XtNjumpProc, Handle_slider, &cd);
  XtAddCallback (cd.leftw, XtNscrollProc, Handle_slam_slider, &cd);
  XtAddCallback (cd.rightw, XtNjumpProc, Handle_slider, &cd);
  XtAddCallback (cd.rightw, XtNscrollProc, Handle_slam_slider, &cd);
  XtAddCallback (cd.lockw, XtNcallback, Handle_lock, &cd);
  XtAddCallback (mic.leftw, XtNjumpProc, Handle_slider, &mic);
  XtAddCallback (mic.leftw, XtNscrollProc, Handle_slam_slider, &mic);
  XtAddCallback (reclvl.leftw, XtNjumpProc, Handle_slider, &reclvl);
  XtAddCallback (reclvl.leftw, XtNscrollProc, Handle_slam_slider, &reclvl);

  XtAddCallback (line_src, XtNcallback, Handle_source,
		 (XtPointer)SOUND_MIXER_LINE);
  XtAddCallback (mic_src, XtNcallback, Handle_source,
		 (XtPointer)SOUND_MIXER_MIC);
  XtAddCallback (cd_src, XtNcallback, Handle_source,
		 (XtPointer)SOUND_MIXER_CD);


  install_pixmaps ();

  /* Open the mixer device */
  mixer_fd = open ("/dev/mixer", O_RDWR, 0);
  if (mixer_fd < 0)
    perror ("Error opening mixer device"), exit (1);

  
  if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &supported) == -1)
    supported = 0xffff;	/* Assume all are supported */

  /*
   * Match the display settings to the current mixer configuration.
   */
  sync_display();

  /*
   * Pick some reasonable lock settings to start with.
   * Two equal volume levels start off with that pair linked.
   */
  
  master.locked = (master.volume.left == master.volume.right);
  bass.locked = (bass.volume.left == bass.volume.right);
  treble.locked = (treble.volume.left == treble.volume.right);
  line.locked = (line.volume.left == line.volume.right);
  dsp.locked = (dsp.volume.left == dsp.volume.right);
  fm.locked = (fm.volume.left == fm.volume.right);
  cd.locked = (cd.volume.left == cd.volume.right);
  mic.locked = 0;
  reclvl.locked = 0;

  printf("supported = 0x%x\n",supported);
  set_supported(&master,(supported & SOUND_MASK_VOLUME) != 0);
  set_supported(&bass,(supported & SOUND_MASK_BASS) != 0);
  set_supported(&treble,(supported & SOUND_MASK_TREBLE) != 0);
  set_supported(&line,(supported & SOUND_MASK_LINE) != 0);
  set_supported(&dsp,(supported & SOUND_MASK_PCM) != 0);
  set_supported(&fm,(supported & SOUND_MASK_SYNTH) != 0);
  set_supported(&cd,(supported & SOUND_MASK_CD) != 0);
  set_supported(&mic,(supported & SOUND_MASK_MIC) != 0);
  set_supported(&reclvl,(supported & SOUND_MASK_RECLEV) != 0);

  /*
   * Update the lock bitmaps to reflect linking
   */
  sync_lock(&master);
  sync_lock(&bass);
  sync_lock(&treble);
  sync_lock(&line);
  sync_lock(&dsp);
  sync_lock(&fm);
  sync_lock(&cd);

  XtRealizeWidget (topLevel);	/* Action! */
  XtAppMainLoop (app_context);	/* Loop for events */
}
