1 /*======================================================================
2   xmix: An X11 interface for Linux derived from:
3 ----------
4    xmix: X interface to the Sound Blaster mixer.
5    [ This file is a part of SBlast-BSD-1.5 ]
6 
7    Steve Haehnichen <shaehnic@ucsd.edu>
8 
9    xmix.c,v 1.5 1992/09/14 03:17:21 steve Exp
10 
11    Copyright (C) 1992 Steve Haehnichen.
12 
13    This program is free software; you can redistribute it and/or modify
14    it under the terms of the GNU General Public License as published by
15    the Free Software Foundation; either version 1, or (at your option)
16    any later version.
17 
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21    GNU General Public License for more details.
22 
23    You should have received a copy of the GNU General Public License
24    along with this program; if not, write to the Free Software
25    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 
27  * xmix.c,v
28  * Revision 1.5  1992/09/14  03:17:21  steve
29  * Released with driver v1.5.
30  * Converted over to stereo CD levels.
31  *
32  * Revision 1.4  1992/06/12  22:33:39  steve
33  * Much better.
34  * Moved many resources into source code.
35  * Ready for release in v1.4
36  *
37  * Revision 1.3  1992/06/08  04:36:26  steve
38  * Works fine.
39  *
40 ----------
41 Linux/Sound driver V2.0 port by Hal Brand (brand@netcom.com or brand1@llnl.gov)
42 Let's call this xmix V2.0 since it is significantly changed.
43 
44 Patches for using the sound driver capabilities from:
45   <rick@digibd.com>
46 I added a little bit of code to have X11 disable the controls but BIG THANKS
47 to Rick! I'll call this version V2.1
48 
49 ======================================================================*/
50 
51 #include <stdio.h>
52 #include <sys/errno.h>
53 #include <fcntl.h>
54 
55 /*
56  * Include files required for all Toolkit programs
57  */
58 #include <X11/Intrinsic.h>	/* Intrinsics Definitions */
59 #include <X11/StringDefs.h>	/* Standard Name-String definitions */
60 #include <X11/Shell.h>		/* Shell Definitions */
61 
62 /*
63  * Public include file for widgets we actually use in this file.
64  */
65 #include <X11/Xaw/Command.h>	/* Athena Command Widget */
66 #include <X11/Xaw/Box.h>
67 #include <X11/Xaw/Form.h>
68 #include <X11/Xaw/Toggle.h>
69 #include <X11/Xaw/Scrollbar.h>
70 #include <X11/Xaw/Label.h>
71 
72 #include "xmix.icon.bit"	/* The icon, of course */
73 #include "locked.bit"		/* L & R locked together (From dirt) */
74 #include "unlocked.bit"		/* L & R free to be different (From dirt)*/
75 #include "circle_on.bit"
76 #include "circle_off.bit"
77 #include "square_empty.bit"
78 #include "square_with_x.bit"
79 
80 #include <sys/soundcard.h>
81 
82 #define SOUND_FULL_SCALE 100.0
83 #define MAX_SOUND_VOL 95
84 #define MIN_SOUND_VOL 05
85 #define SLIDER_LENGTH (0.05)
86 
87 
88 
89 /*
90  * Here's the debugging macro I use here and there, so I can turn
91  * them all on or off in one place.
92  */
93 /* #define DEBUG */
94 #ifdef DEBUG
95 #define DPRINTF(x)      printf x
96 #else
97 #define DPRINTF(x)
98 #endif
99 
100 
101 /*
102  * Some of these Xt names are too long for nice formatting
103  */
104 #define MW 		XtVaCreateManagedWidget
105 #define PORTION(val)	(1.0 - (float)val / SOUND_FULL_SCALE)
106 #define MK_BITMAP(bits, width, height) \
107   XCreateBitmapFromData (XtDisplay (topLevel), \
108 			 RootWindowOfScreen (XtScreen (topLevel)),\
109 			 bits, width, height)
110 #define SCROLLBAR_RES	 XtNwidth, 23, XtNheight, 100
111 #define CENTER(widg)	 XtVaSetValues (widg, XtNwidth,\
112 					centering_width, NULL)
113 
114 
115 typedef struct stereovolume
116 {
117   unsigned char left;
118   unsigned char right;
119 } StereoVolume;
120 
121 
122 typedef struct volctrl
123 {
124   int mixer_id;
125   Widget formw;
126   Widget labelw;
127   Widget leftw;
128   Widget rightw;
129   Widget lockw;
130   int locked;
131   StereoVolume volume;
132   int supported;
133 } VolumeControl;
134 
135 
136 
137 /*
138  * I'm lazy.  Everything is global today.
139  */
140 Pixmap icon_pixmap, locked_pixmap, unlocked_pixmap;
141 Pixmap circle_on_pixmap, circle_off_pixmap;
142 Pixmap square_empty_pixmap, square_with_x_pixmap;
143 Widget topLevel, quit, whole, buttons, sliders;
144 Widget sources, line_src, mic_src, cd_src, source_label;
145 Widget line_src_label, mic_src_label, cd_src_label;
146 int centering_width;
147 int mixer_fd;
148 
149 VolumeControl master, line, dsp, fm, cd, mic, bass, treble, reclvl;
150 
151 
152 
153 /*
154  * Convert all the pixmap data into Pixmap objects.
155  */
install_pixmaps(void)156 static void install_pixmaps (void)
157 {
158   /* The icon bitmap */
159   icon_pixmap = MK_BITMAP (xmix_bits, xmix_width, xmix_height);
160   XtVaSetValues (topLevel, XtNiconPixmap, icon_pixmap, NULL);
161 
162   locked_pixmap = MK_BITMAP (locked_bits, locked_width, locked_height);
163   unlocked_pixmap = MK_BITMAP (unlocked_bits, unlocked_width, unlocked_height);
164   circle_on_pixmap =
165     MK_BITMAP (circle_on_bits, circle_on_width, circle_on_height);
166   circle_off_pixmap =
167     MK_BITMAP (circle_off_bits, circle_off_width, circle_off_height);
168   square_empty_pixmap =
169     MK_BITMAP (square_empty_bits, square_empty_width, square_empty_height);
170   square_with_x_pixmap =
171     MK_BITMAP (square_with_x_bits, square_with_x_width, square_with_x_height);
172 }
173 
174 
175 
sync_slider(VolumeControl * vcptr)176 static void sync_slider(VolumeControl *vcptr)
177 {
178   float portion;
179 
180   if (!vcptr->supported)
181     return;
182   if (ioctl(mixer_fd,MIXER_READ(vcptr->mixer_id),&vcptr->volume) == -1)
183     perror("Error reading volumes in sync_slider");
184   portion = PORTION(vcptr->volume.left);
185   XawScrollbarSetThumb (vcptr->leftw, portion, SLIDER_LENGTH);
186   if (vcptr->rightw != NULL)
187     {
188       portion = PORTION(vcptr->volume.right);
189       XawScrollbarSetThumb (vcptr->rightw, portion, SLIDER_LENGTH);
190     }
191   return;
192 }
193 
194 
195 
196 
sync_lock(VolumeControl * vcptr)197 static void sync_lock(VolumeControl *vcptr)
198 {
199   if (vcptr->locked)
200       XtVaSetValues (vcptr->lockw, XtNbitmap, locked_pixmap, NULL);
201   else
202       XtVaSetValues (vcptr->lockw, XtNbitmap, unlocked_pixmap, NULL);
203   CENTER (vcptr->lockw);
204 }
205 
206 
207 
Handle_lock(Widget w,XtPointer client_data,XtPointer call_data)208 static void Handle_lock(Widget w, XtPointer client_data, XtPointer call_data)
209 {
210   VolumeControl *vcptr = client_data;
211 
212   vcptr->locked = !(vcptr->locked);
213   if (vcptr->locked && vcptr->volume.left != vcptr->volume.right)
214     {
215       vcptr->volume.left = vcptr->volume.right =
216 	((vcptr->volume.left + vcptr->volume.right)/2);
217       if (ioctl(mixer_fd,MIXER_WRITE(vcptr->mixer_id),&vcptr->volume) == -1)
218 	perror("Error in Handle_lock writing volumes");
219       sync_slider(vcptr);
220     }
221   sync_lock(vcptr);
222 }
223 
224 
225 
226 /*
227  * Match the shown button to the mixer's idea of the recording source.
228  */
sync_source_display(void)229 static void sync_source_display(void)
230 {
231   int source,id;
232 
233   if (ioctl(mixer_fd,SOUND_MIXER_READ_RECSRC,&source) == -1)
234     perror("Error reading mixer recording source");
235   DPRINTF(("Current recording source is %d\n",source));
236   id = 0;
237   if (source == 0)
238     {
239       id = SOUND_MIXER_MIC;
240       source = 1 << id;
241       if (ioctl(mixer_fd,SOUND_MIXER_WRITE_RECSRC,&source) == -1)
242 	perror("Error setting mixer recording source to MIC!");
243     }
244   else
245     {
246       while ((source & 1) == 0)
247 	{
248 	  source = source>>1;
249 	  ++id;
250 	}
251     }
252   DPRINTF(("Current recording source ID=%d\n",id));
253   switch (id)
254     {
255     case SOUND_MIXER_MIC:
256       XtVaSetValues (mic_src, XtNbitmap, circle_on_pixmap, NULL);
257       XtVaSetValues (cd_src, XtNbitmap, circle_off_pixmap, NULL);
258       XtVaSetValues (line_src, XtNbitmap, circle_off_pixmap, NULL);
259       break;
260     case SOUND_MIXER_CD:
261       XtVaSetValues (mic_src, XtNbitmap, circle_off_pixmap, NULL);
262       XtVaSetValues (cd_src, XtNbitmap, circle_on_pixmap, NULL);
263       XtVaSetValues (line_src, XtNbitmap, circle_off_pixmap, NULL);
264       break;
265     case SOUND_MIXER_LINE:
266       XtVaSetValues (mic_src, XtNbitmap, circle_off_pixmap, NULL);
267       XtVaSetValues (cd_src, XtNbitmap, circle_off_pixmap, NULL);
268       XtVaSetValues (line_src, XtNbitmap, circle_on_pixmap, NULL);
269       break;
270     default:
271       fprintf (stderr, "Invalid recording source!\n");
272     }
273   return;
274 }
275 
276 
277 
278 
279 /*
280  * Callback for selecting a new source.
281  * Basically, we set the mixer to the new source, and then
282  * let sync_source_display handle the visual feedback.
283  * (Easier than radioGroups.)
284  */
Handle_source(Widget w,XtPointer client_data,XtPointer call_data)285 static void Handle_source(Widget w, XtPointer client_data,
286 			  XtPointer call_data)
287 {
288   int source = 1 << ((int) client_data);
289 
290   DPRINTF(("Setting recording source to %d (%d)\n",source,(int)client_data));
291   if (ioctl(mixer_fd,SOUND_MIXER_WRITE_RECSRC,&source) == -1)
292     perror("Error writing mixer recording source");
293 
294   sync_source_display();
295 }
296 
297 
298 
299 
300 /*
301  * Quit button callback function
302  */
Quit(Widget w,XtPointer client_data,XtPointer call_data)303 void Quit(Widget w, XtPointer client_data, XtPointer call_data)
304 {
305   DPRINTF (("Exiting...\n"));
306   exit (0);
307 }
308 
309 
310 
set_slider(VolumeControl * vcptr,Widget w,int value)311 static void set_slider(VolumeControl *vcptr, Widget w, int value)
312 {
313   float portion = PORTION(value);
314   int update_needed;
315 
316   DPRINTF (("set_slider: val = %d, portion = %f\n",value,portion));
317 
318   if (vcptr->locked)
319     {
320       update_needed = (vcptr->volume.left != value);
321       DPRINTF (("set_slider: locked; updating\n"));
322       vcptr->volume.left = vcptr->volume.right = value;
323       XawScrollbarSetThumb(vcptr->leftw, portion, SLIDER_LENGTH);
324       XawScrollbarSetThumb(vcptr->rightw, portion, SLIDER_LENGTH);
325     }
326   else
327     {
328       if (vcptr->leftw == w)
329 	{
330 	  update_needed = (vcptr->volume.left != value);
331 	  vcptr->volume.left = value;
332 	}
333       else
334 	{
335 	  update_needed = (vcptr->volume.right != value);
336 	  vcptr->volume.right = value;
337 	}
338       XawScrollbarSetThumb(w, portion, SLIDER_LENGTH);
339     }
340 
341   /*
342    * If the new value is at a different notch than the current setting,
343    * then inform the mixer, and adopt the setting.
344    * Otherwise, don't waste time setting the mixer.
345    */
346   if (update_needed)
347     {
348       DPRINTF (("set_slider: updating mixer\n"));
349       if (vcptr->supported
350         && ioctl(mixer_fd,MIXER_WRITE(vcptr->mixer_id),&vcptr->volume) == -1)
351 	perror("Error writing mixer in Handle_slider");
352     }
353   return;
354 }
355 
356 
357 
358 /*
359  * This is for pushing a slider to MAX or MIN position.
360  * Of questionable utility, yes..
361  */
Handle_slam_slider(Widget w,XtPointer client_data,XtPointer call_data)362 static void Handle_slam_slider(Widget w, XtPointer client_data,
363 			       XtPointer call_data)
364 {
365   int val;
366   VolumeControl *vcptr = client_data;
367 
368   val = (vcptr->leftw == w) ? vcptr->volume.left : vcptr->volume.right;
369   if ((int)call_data < 0)
370     {
371       if (val >= MAX_SOUND_VOL)
372 	val = 10*((MAX_SOUND_VOL-1)/10);
373       else
374 	val -= 10;
375       if (val < MIN_SOUND_VOL) val = MIN_SOUND_VOL;
376     }
377   else
378     {
379       if (val <= MIN_SOUND_VOL)
380 	val = 10*((MIN_SOUND_VOL+9)/10);
381       else
382 	val += 10;
383       if (val > MAX_SOUND_VOL) val = MAX_SOUND_VOL;
384     }
385 
386   set_slider(vcptr,w,val);
387   return;
388 }
389 
390 
391 
392 /*
393  * XtNjumpProc callback for volume fader scrollbar widgets.
394  * Great pains are taken to make the slider accurately reflect
395  * the granular mixer setting, without actually querying the mixer
396  * device.  (This is faster.)
397  */
Handle_slider(Widget w,XtPointer client_data,XtPointer call_data)398 static void Handle_slider(Widget w, XtPointer client_data,
399 			  XtPointer call_data)
400 {
401   int val;
402   VolumeControl *vcptr = client_data;
403 
404   DPRINTF (("Handle_slider got %f\n", *(float*)call_data));
405   val = (int)(.5 + (SOUND_FULL_SCALE * (1.0 - *(float*)call_data)));
406   if (val > MAX_SOUND_VOL)
407     val = MAX_SOUND_VOL;
408   else if (val < MIN_SOUND_VOL)
409     val = MIN_SOUND_VOL;
410   set_slider(vcptr,w,val);
411   return;
412 }
413 
414 
415 
sync_sliders(void)416 static void sync_sliders(void)
417 {
418   sync_slider(&master);
419   sync_slider(&bass);
420   sync_slider(&treble);
421   sync_slider(&line);
422   sync_slider(&dsp);
423   sync_slider(&fm);
424   sync_slider(&cd);
425   sync_slider(&mic);
426   sync_slider(&reclvl);
427 }
428 
429 
430 
431 
432 /*
433  * Rescan the mixer settings and make all the indicators reflect
434  * the current values.
435  */
sync_display(void)436 static void sync_display(void)
437 {
438   DPRINTF(("Updating..\n"));
439   sync_source_display();
440   sync_sliders();
441 }
442 
443 
444 
set_supported(VolumeControl * vcptr,int is_sup)445 static void set_supported(VolumeControl *vcptr, int is_sup)
446 {
447   vcptr->supported = is_sup;
448   XtSetSensitive(vcptr->formw,is_sup);
449   return;
450 }
451 
452 
main(int argc,char ** argv)453 void main (int argc, char **argv)
454 {
455   XtAppContext app_context;
456   int scroll_sep, longway;
457   Widget version;
458   int supported;
459   char mixer_name[32];
460 
461   {
462     int i;
463 
464     longway=0;
465     strcpy(mixer_name, "/dev/mixer");
466 
467     i=1;
468     while (i<argc) {
469       if (strcmp(argv[i],"-l")==0) longway=1;
470 
471       if (strcmp(argv[i],"-device")==0) {
472 	i++;
473 	if (i>=argc) {
474 	  fprintf(stderr, "Error: no device specified\n");
475 	  exit(1);
476 	}
477 	strcpy(mixer_name, argv[i]);
478       }
479       i++;
480     };
481   }
482 
483   topLevel = XtVaAppInitialize (&app_context,
484      "XMix",			/* Application class */
485      NULL, 0,			/* command line option list */
486      &argc, argv,		/* command line args */
487      NULL,			/* for missing app-defaults file */
488      NULL);			/* terminate varargs list */
489 
490 
491   whole = MW ("whole", formWidgetClass, topLevel, NULL);
492 
493   sliders = MW ("sliders", formWidgetClass, whole, NULL);
494 
495   master.mixer_id = SOUND_MIXER_VOLUME;
496   master.formw = MW ("master_form", formWidgetClass, sliders, NULL);
497   master.labelw =  MW ("master_label", labelWidgetClass, master.formw,
498 		       XtNlabel, "Master", NULL);
499   master.leftw = MW ("master_l", scrollbarWidgetClass, master.formw,
500 		     SCROLLBAR_RES, XtNfromVert, master.labelw,NULL);
501   master.rightw = MW ("master_r", scrollbarWidgetClass, master.formw,
502 		      SCROLLBAR_RES, XtNfromHoriz, master.leftw,
503 		      XtNfromVert, master.labelw, NULL);
504   master.lockw = MW ("master_lock", commandWidgetClass, master.formw,
505 		     XtNfromVert, master.leftw, NULL);
506 
507   bass.mixer_id = SOUND_MIXER_BASS;
508   bass.formw = MW ("bass_form", formWidgetClass, sliders,
509 		   XtNfromHoriz, master.formw, NULL);
510   bass.labelw =  MW ("bass_label", labelWidgetClass, bass.formw,
511 		     XtNlabel, "Bass", NULL);
512   bass.leftw = MW ("bass_l", scrollbarWidgetClass, bass.formw, SCROLLBAR_RES,
513 		   XtNfromVert, bass.labelw, NULL);
514   bass.rightw = MW ("bass_r", scrollbarWidgetClass, bass.formw, SCROLLBAR_RES,
515 		    XtNfromHoriz, bass.leftw, XtNfromVert, bass.labelw, NULL);
516   bass.lockw = MW ("bass_lock", commandWidgetClass, bass.formw,
517 		   XtNfromVert, bass.leftw, NULL);
518 
519   treble.mixer_id = SOUND_MIXER_TREBLE;
520   treble.formw = MW ("treble_form", formWidgetClass, sliders,
521 		     XtNfromHoriz, bass.formw, NULL);
522   treble.labelw =  MW ("treble_label", labelWidgetClass, treble.formw,
523 		       XtNlabel, "Treble", NULL);
524   treble.leftw = MW ("treble_l", scrollbarWidgetClass, treble.formw,
525 		     SCROLLBAR_RES, XtNfromVert, treble.labelw, NULL);
526   treble.rightw = MW ("treble_r", scrollbarWidgetClass, treble.formw,
527 		      SCROLLBAR_RES, XtNfromHoriz, treble.leftw,
528 		      XtNfromVert, treble.labelw, NULL);
529   treble.lockw = MW ("treble_lock", commandWidgetClass, treble.formw,
530 		     XtNfromVert, treble.leftw, NULL);
531 
532   line.mixer_id = SOUND_MIXER_LINE;
533   line.formw = MW ("line_form", formWidgetClass, sliders,
534 		   XtNfromHoriz, treble.formw, NULL);
535   line.labelw =  MW ("line_label", labelWidgetClass, line.formw,
536 		     XtNlabel, "Line", NULL);
537   line.leftw = MW ("line_l", scrollbarWidgetClass, line.formw, SCROLLBAR_RES,
538 		   XtNfromVert, line.labelw, NULL);
539   line.rightw = MW ("line_r", scrollbarWidgetClass, line.formw, SCROLLBAR_RES,
540 		    XtNfromHoriz, line.leftw, XtNfromVert, line.labelw, NULL);
541   line.lockw = MW ("line_lock", commandWidgetClass, line.formw,
542 		   XtNfromVert, line.leftw, NULL);
543 
544   dsp.mixer_id = SOUND_MIXER_PCM;
545   dsp.formw = MW ("dsp_form", formWidgetClass, sliders,
546 		  longway ? XtNfromHoriz : XtNfromVert,
547 		  longway ? line.formw : master.formw,
548 		  NULL);
549   dsp.labelw =  MW ("dsp_label", labelWidgetClass, dsp.formw,
550 		   XtNlabel, "DSP", NULL);
551   dsp.leftw = MW ("dsp_l", scrollbarWidgetClass, dsp.formw, SCROLLBAR_RES,
552 	      XtNfromVert, dsp.labelw, NULL);
553   dsp.rightw = MW ("dsp_r", scrollbarWidgetClass, dsp.formw, SCROLLBAR_RES,
554 	      XtNfromVert, dsp.labelw,
555 	      XtNfromHoriz, dsp.leftw, NULL);
556   dsp.lockw = MW ("dsp_lock", commandWidgetClass, dsp.formw,
557 		 XtNfromVert, dsp.leftw, NULL);
558 
559   fm.mixer_id = SOUND_MIXER_SYNTH;
560   fm.formw = MW ("fm_form", formWidgetClass, sliders,
561 		 XtNfromHoriz, dsp.formw,
562 		 longway ? NULL : XtNfromVert, bass.formw, NULL);
563   fm.labelw =  MW ("fm_label", labelWidgetClass, fm.formw,
564 		  XtNlabel, "FM", NULL);
565   fm.leftw = MW ("fm_l", scrollbarWidgetClass, fm.formw, SCROLLBAR_RES,
566 	     XtNfromVert, fm.labelw, NULL);
567   fm.rightw = MW ("fm_r", scrollbarWidgetClass, fm.formw, SCROLLBAR_RES,
568 	     XtNfromVert, fm.labelw,
569 	     XtNfromHoriz, fm.leftw, NULL);
570   fm.lockw = MW ("fm_lock", commandWidgetClass, fm.formw,
571 		XtNfromVert, fm.leftw, NULL);
572 
573   cd.mixer_id = SOUND_MIXER_CD;
574   cd.formw = MW ("cd_form", formWidgetClass, sliders,
575 		 XtNfromHoriz, fm.formw,
576 		 longway ? NULL : XtNfromVert, treble.formw, NULL);
577   cd.labelw =  MW ("cd_label", labelWidgetClass, cd.formw,
578 		  XtNlabel, "CD", NULL);
579   cd.leftw = MW ("cd_l", scrollbarWidgetClass, cd.formw, SCROLLBAR_RES,
580 	     XtNfromVert, cd.labelw, NULL);
581   cd.rightw = MW ("cd_r", scrollbarWidgetClass, cd.formw, SCROLLBAR_RES,
582 	     XtNfromVert, cd.labelw, XtNfromHoriz, cd.leftw, NULL);
583   cd.lockw = MW ("cd_lock", commandWidgetClass, cd.formw,
584 		XtNfromVert, cd.leftw, NULL);
585 
586   mic.mixer_id = SOUND_MIXER_MIC;
587   mic.formw = MW ("mic_form", formWidgetClass, sliders,
588 		  XtNfromHoriz, cd.formw,
589 		  longway ? NULL : XtNfromVert, line.formw, NULL);
590   mic.labelw =  MW ("mic_label", labelWidgetClass, mic.formw,
591 		   XtNlabel, "Mic", NULL);
592   mic.leftw = MW ("mic_level", scrollbarWidgetClass, mic.formw, SCROLLBAR_RES,
593 		  XtNfromVert, mic.labelw, NULL);
594   mic.rightw = NULL;
595   mic.lockw = NULL;
596 
597   reclvl.mixer_id = SOUND_MIXER_RECLEV;
598   reclvl.formw = MW ("reclvl_form", formWidgetClass, sliders,
599 		     XtNfromHoriz, mic.formw,
600 		     longway ? NULL : XtNfromVert, line.formw, NULL);
601   reclvl.labelw =  MW ("reclvl_label", labelWidgetClass, reclvl.formw,
602 		       XtNlabel, "Reclvl", NULL);
603   reclvl.leftw = MW ("reclvl_level", scrollbarWidgetClass, reclvl.formw,
604 		     SCROLLBAR_RES, XtNfromVert, reclvl.labelw, NULL);
605   reclvl.rightw = NULL;
606   reclvl.lockw = NULL;
607 
608   buttons = MW ("buttons", formWidgetClass, whole,
609 		XtNfromHoriz, sliders, NULL);
610 
611 
612   version = MW ("Version", labelWidgetClass, buttons,
613 		XtNlabel, "XMix V2.1", NULL);
614 
615   sources = MW ("sources", formWidgetClass, buttons,
616 		XtNfromVert, version, NULL);
617   source_label = MW ("source_label", labelWidgetClass, sources,
618 		     XtNlabel, "Recording Source", NULL);
619   line_src = MW ("line_src", commandWidgetClass, sources,
620 		 XtNfromVert, source_label, NULL);
621   line_src_label = MW ("line_src_label", labelWidgetClass, sources,
622 		       XtNlabel, "Line In",
623 		       XtNfromVert, source_label,
624 		       XtNfromHoriz, line_src, NULL);
625   mic_src = MW ("mic_src", commandWidgetClass, sources,
626 		XtNfromVert, line_src, NULL);
627   mic_src_label = MW ("mic_src_label", labelWidgetClass, sources,
628 		      XtNlabel, "Microphone",
629 		      XtNfromVert, line_src,
630 		      XtNfromHoriz, mic_src, NULL);
631   cd_src = MW ("cd_src", commandWidgetClass, sources,
632 	       XtNfromVert, mic_src, NULL);
633   cd_src_label = MW ("cd_src_label", labelWidgetClass, sources,
634 		     XtNlabel, "CD",
635 		     XtNfromVert, mic_src,
636 		     XtNfromHoriz, cd_src, NULL);
637   quit = MW ("quit", commandWidgetClass, buttons,
638 	     XtNfromVert, sources,
639 	     XtNlabel, "Quit",
640 	     XtNwidth, 50,
641 	     XtNheight, 20,
642 	     NULL);
643 
644 
645   XtVaGetValues (mic.leftw, XtNwidth, &centering_width, NULL);
646   CENTER (mic.labelw);
647   XtVaGetValues (reclvl.leftw, XtNwidth, &centering_width, NULL);
648   CENTER (reclvl.labelw);
649   XtVaGetValues (master.formw, XtNhorizDistance, &scroll_sep, NULL);
650   XtVaGetValues (master.leftw, XtNwidth, &centering_width, NULL);
651   centering_width = centering_width * 2 + scroll_sep;
652   CENTER (master.labelw);
653   CENTER (bass.labelw);
654   CENTER (treble.labelw);
655   CENTER (line.labelw);
656   CENTER (dsp.labelw);
657   CENTER (fm.labelw);
658   CENTER (cd.labelw);
659 
660   XtAddEventHandler (topLevel, EnterWindowMask, FALSE,
661 		     (XtEventHandler) sync_display, NULL);
662   XtAddCallback (quit, XtNcallback, Quit, 0 /* client_data */ );
663   XtAddCallback (master.leftw, XtNjumpProc, Handle_slider, &master);
664   XtAddCallback (master.leftw, XtNscrollProc, Handle_slam_slider, &master);
665   XtAddCallback (master.rightw, XtNjumpProc, Handle_slider, &master);
666   XtAddCallback (master.rightw, XtNscrollProc, Handle_slam_slider, &master);
667   XtAddCallback (master.lockw, XtNcallback, Handle_lock, &master);
668   XtAddCallback (bass.leftw, XtNjumpProc, Handle_slider, &bass);
669   XtAddCallback (bass.leftw, XtNscrollProc, Handle_slam_slider, &bass);
670   XtAddCallback (bass.rightw, XtNjumpProc, Handle_slider, &bass);
671   XtAddCallback (bass.rightw, XtNscrollProc, Handle_slam_slider, &bass);
672   XtAddCallback (bass.lockw, XtNcallback, Handle_lock, &bass);
673   XtAddCallback (treble.leftw, XtNjumpProc, Handle_slider, &treble);
674   XtAddCallback (treble.leftw, XtNscrollProc, Handle_slam_slider, &treble);
675   XtAddCallback (treble.rightw, XtNjumpProc, Handle_slider, &treble);
676   XtAddCallback (treble.rightw, XtNscrollProc, Handle_slam_slider, &treble);
677   XtAddCallback (treble.lockw, XtNcallback, Handle_lock, &treble);
678   XtAddCallback (line.leftw, XtNjumpProc, Handle_slider, &line);
679   XtAddCallback (line.leftw, XtNscrollProc, Handle_slam_slider, &line);
680   XtAddCallback (line.rightw, XtNjumpProc, Handle_slider, &line);
681   XtAddCallback (line.rightw, XtNscrollProc, Handle_slam_slider, &line);
682   XtAddCallback (line.lockw, XtNcallback, Handle_lock, &line);
683   XtAddCallback (dsp.leftw, XtNjumpProc, Handle_slider, &dsp);
684   XtAddCallback (dsp.leftw, XtNscrollProc, Handle_slam_slider, &dsp);
685   XtAddCallback (dsp.rightw, XtNjumpProc, Handle_slider, &dsp);
686   XtAddCallback (dsp.rightw, XtNscrollProc, Handle_slam_slider, &dsp);
687   XtAddCallback (dsp.lockw, XtNcallback, Handle_lock, &dsp);
688   XtAddCallback (fm.leftw, XtNjumpProc, Handle_slider, &fm);
689   XtAddCallback (fm.leftw, XtNscrollProc, Handle_slam_slider, &fm);
690   XtAddCallback (fm.rightw, XtNjumpProc, Handle_slider, &fm);
691   XtAddCallback (fm.rightw, XtNscrollProc, Handle_slam_slider, &fm);
692   XtAddCallback (fm.lockw, XtNcallback, Handle_lock, &fm);
693   XtAddCallback (cd.leftw, XtNjumpProc, Handle_slider, &cd);
694   XtAddCallback (cd.leftw, XtNscrollProc, Handle_slam_slider, &cd);
695   XtAddCallback (cd.rightw, XtNjumpProc, Handle_slider, &cd);
696   XtAddCallback (cd.rightw, XtNscrollProc, Handle_slam_slider, &cd);
697   XtAddCallback (cd.lockw, XtNcallback, Handle_lock, &cd);
698   XtAddCallback (mic.leftw, XtNjumpProc, Handle_slider, &mic);
699   XtAddCallback (mic.leftw, XtNscrollProc, Handle_slam_slider, &mic);
700   XtAddCallback (reclvl.leftw, XtNjumpProc, Handle_slider, &reclvl);
701   XtAddCallback (reclvl.leftw, XtNscrollProc, Handle_slam_slider, &reclvl);
702 
703   XtAddCallback (line_src, XtNcallback, Handle_source,
704 		 (XtPointer)SOUND_MIXER_LINE);
705   XtAddCallback (mic_src, XtNcallback, Handle_source,
706 		 (XtPointer)SOUND_MIXER_MIC);
707   XtAddCallback (cd_src, XtNcallback, Handle_source,
708 		 (XtPointer)SOUND_MIXER_CD);
709 
710 
711   install_pixmaps ();
712 
713   /* Open the mixer device */
714   mixer_fd = open (mixer_name, O_RDWR, 0);
715   if (mixer_fd < 0) {
716     char msg[32];
717     sprintf(msg, "Error opening mixer device %s", mixer_name);
718     perror (msg), exit (1);
719   }
720 
721 
722   if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &supported) == -1)
723     supported = 0xffff;	/* Assume all are supported */
724 
725   /*
726    * Match the display settings to the current mixer configuration.
727    */
728   sync_display();
729 
730   /*
731    * Pick some reasonable lock settings to start with.
732    * Two equal volume levels start off with that pair linked.
733    */
734 
735   master.locked = (master.volume.left == master.volume.right);
736   bass.locked = (bass.volume.left == bass.volume.right);
737   treble.locked = (treble.volume.left == treble.volume.right);
738   line.locked = (line.volume.left == line.volume.right);
739   dsp.locked = (dsp.volume.left == dsp.volume.right);
740   fm.locked = (fm.volume.left == fm.volume.right);
741   cd.locked = (cd.volume.left == cd.volume.right);
742   mic.locked = 0;
743   reclvl.locked = 0;
744 
745   printf("supported = 0x%x\n",supported);
746   set_supported(&master,(supported & SOUND_MASK_VOLUME) != 0);
747   set_supported(&bass,(supported & SOUND_MASK_BASS) != 0);
748   set_supported(&treble,(supported & SOUND_MASK_TREBLE) != 0);
749   set_supported(&line,(supported & SOUND_MASK_LINE) != 0);
750   set_supported(&dsp,(supported & SOUND_MASK_PCM) != 0);
751   set_supported(&fm,(supported & SOUND_MASK_SYNTH) != 0);
752   set_supported(&cd,(supported & SOUND_MASK_CD) != 0);
753   set_supported(&mic,(supported & SOUND_MASK_MIC) != 0);
754   set_supported(&reclvl,(supported & SOUND_MASK_RECLEV) != 0);
755 
756   /*
757    * Update the lock bitmaps to reflect linking
758    */
759   sync_lock(&master);
760   sync_lock(&bass);
761   sync_lock(&treble);
762   sync_lock(&line);
763   sync_lock(&dsp);
764   sync_lock(&fm);
765   sync_lock(&cd);
766 
767   XtRealizeWidget (topLevel);	/* Action! */
768   XtAppMainLoop (app_context);	/* Loop for events */
769 }
770