/*****************************************************************************/
/*	ascd - AfterStep Mixer                                               */
/*	Version 0.5						             */
/*	By Rob Malda                                                  	     */
/*	malda@cs.hope.edu						     */
/*	http://www.cs.hope.edu/~malda/					     */
/*      and Chris Jantzen                                                    */
/*      cjantzen@geocities.com                                               */
/*      http://www.geocities.com/SiliconValley/Pines/4510/                   */
/*									     */
/*	This is an 'AfterStep Look & Feel' Wharf style applet that can be    */
/*	used to control /dev/mixer.           				     */
/*									     */
/*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>       
#include <string.h>
#include <math.h>

#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>

#include <errno.h>
#include <unistd.h>

#include <sys/mount.h>
#include <linux/soundcard.h>

  
/* XPM struct and icons ******************************************************/
typedef struct _XpmIcon {
	Pixmap pixmap;
	Pixmap mask;
	XpmAttributes attributes;
} XpmIcon;

XpmIcon buttonXPM,  iconXPM, currentXPM;

#include "XPM/button.xpm"
#include "XPM/icon.xpm"
/* Functions *****************************************************************/
void	Help(void);
void	CreateWindow(void);
void	ParseCmdLine(int argc, char *argv[]);
void	MainLoop();
void	GetXPM(void);
int	FlushExpose(Window w);
void	RedrawWindow();
Pixel	GetColor(char *name);
void	setvol(int control, int setto);
void	setvol_command(int control, int setto);
void	getvol();

/* Global stuff **************************************************************/
#define TRUE 1;
#define FALSE 0;
#define DEFAULTDEVICE "/dev/mixer";
#define DEFAULTTITLE "asmixer"

int     withdrawn=FALSE;
int	volbutloc[3];
int	voldev[3];
int	volold[3];
int     vol[3];
char    wintitle[128] = DEFAULTTITLE;
int	redraw=TRUE;
int	changedmask=TRUE;
int     mouseDown=FALSE;
Display	*Disp;	 
Window	Root;
Window	Iconwin;
Window	Win;
char	*Geometry = 0;
char    device[128]=DEFAULTDEVICE;
GC	WinGC;
int     CarrierOn = FALSE;

/*****************************************************************************/
int main(int argc,char *argv[])
{
	voldev[0]=SOUND_MIXER_VOLUME;
	voldev[1]=SOUND_MIXER_CD;
	voldev[2]=SOUND_MIXER_PCM;

	ParseCmdLine(argc, argv);   

	getvol();  
        CreateWindow();
        XSetCommand(Disp,Win,argv,argc); 
	MainLoop();
	return 0;
}

void getvol()
{
   int fd;
   int status=0;
   int x;       

   fd=open(device,0);
   if(fd < 0)
      {
      fprintf(stderr,"asmixer:unable to grab %s\n", device);
      return;
      }
   for(x=0;x<3;x++)
      {
      if (ioctl(fd,MIXER_READ(voldev[x]),&vol[x]) < 0)
	  status = -1;
      vol[x]=vol[x] >> 8;
      volbutloc[x]=37 - (vol[x] * 37) / 100;
      }
   close(fd);                 
   if(status < 0)
      {
      fprintf(stderr,"asmixer:error reading from %s\n", device);
      return;
      }
#ifdef DEBUG
   fprintf(stderr, "vol %i old %i \n",vol[0], volold[1]);
#endif
   for(x=0;x<3;x++)
      if(volold[x]!=vol[x]) changedmask=redraw=TRUE; 

}

void setvol_command(int control, int setto) /* No REDRAW because there is no */
{					    /* window yet... */
int fd;

if (setto > 37) setto=37;
if (setto < 0) setto=0;    

fd=open(device,0);

vol[control]   =100- ((setto * 100) / 37);
volold[control]=vol[control] << 8;
vol[control]=volold[control] | vol[control];
ioctl(fd,MIXER_WRITE(voldev[control]),&vol[control]);

volbutloc[control]=setto;
close(fd);
}

void setvol(int control, int setto)
{
int fd;

if (setto > 37) setto=37;
if (setto < 0) setto=0;    

fd=open(device,0);

vol[control]   =100- ((setto * 100) / 37);
volold[control]=vol[control] << 8;
vol[control]=volold[control] | vol[control];
ioctl(fd,MIXER_WRITE(voldev[control]),&vol[control]);

volbutloc[control]=setto;
redraw=TRUE;
changedmask=TRUE;
close(fd);
RedrawWindow();
}

/*****************************************************************************/
void Help()
{       
	fprintf(stderr,"asmixer - Version 0.5\n");
	fprintf(stderr,"by Rob Malda - malda@cs.hope.edu\n");
	fprintf(stderr,"http://www.cs.hope.edu/~malda/\n");
	fprintf(stderr,"and Chris Jantzen - cjantzen@geocities.com\n");
	fprintf(stderr,"http://www.geocities.com/SiliconValley/Pines/4510/\n\n");
	fprintf(stderr,"usage:  asmixer [-options ...] \n");
	fprintf(stderr,"options:\n");
	fprintf(stderr,"\n");       
	fprintf(stderr,"  Command                 Default    What\n");
	fprintf(stderr,"g Geometry                none       Standard X Location\n");	
	fprintf(stderr,"d Device                  /dev/mixer The Mixer Device\n");
	fprintf(stderr,"1 Controls                VOLUME     Main Volume Control\n");
	fprintf(stderr,"2 Controls                CD         CD Player Volume\n");
	fprintf(stderr,"3 Controls                PCM        Wave Audio Volume\n");
	fprintf(stderr,"t Title                   asmixer    Window title\n");
 	fprintf(stderr,"v rate (0-100)                       Set default volume for control 1\n");
        fprintf(stderr,"w Withdrawn               none      Starts window in Withdrawn State (for WindowMaker)"); 
 	fprintf(stderr,"                                     (default VOLUME).\n\n");                        
        fprintf(stderr,"for -1, -2, -3 parameter must be one of VOLUME, BASS, TREBLE, SYNTH, PCM,\n");
	fprintf(stderr,"SPEAKER, LINE, MIC, CD, IMIX, ALTPCM, RECLEV, IGAIN, OGAIN, LINE1, LINE2,\n");
	fprintf(stderr,"or LINE3.)\n\n");
	fprintf(stderr,"\n\n");
	exit(1); 
}

/****************************************************************************/
void CreateWindow(void)
{
	int i;
	unsigned int borderwidth;
	char *wname = (char *)&wintitle;
	char *display_name = NULL;
	XGCValues gcv;
	unsigned long gcm;
	XTextProperty name;
	Pixel back_pix, fore_pix;
	Pixmap pixmask;
	int screen;	
	int x_fd;
	int d_depth;
	int ScreenWidth, ScreenHeight;
	XSizeHints SizeHints;
	XWMHints WmHints;
        XClassHint classHint;	

	/* Open display */
	if (!(Disp = XOpenDisplay(display_name)))  
	{ 
		fprintf(stderr,"asmixer: can't open display %s\n", XDisplayName(display_name));
		exit (1); 
	} 
	
	screen = DefaultScreen(Disp);
	Root = RootWindow(Disp, screen);
	d_depth = DefaultDepth(Disp, screen);
	x_fd = XConnectionNumber(Disp);	
	ScreenHeight = DisplayHeight(Disp,screen);
	ScreenWidth = DisplayWidth(Disp,screen);
	       
	GetXPM();
		
	SizeHints.flags= USSize|USPosition;
	SizeHints.x = 0;
	SizeHints.y = 0;	
	back_pix = GetColor("white");
	fore_pix = GetColor("black");
	
	XWMGeometry(Disp, screen, Geometry, NULL, (borderwidth =1), &SizeHints,
		    &SizeHints.x,&SizeHints.y,&SizeHints.width,
		    &SizeHints.height, &i); 	
	
	SizeHints.width = iconXPM.attributes.width;
	SizeHints.height= iconXPM.attributes.height;	
	Win = XCreateSimpleWindow(Disp,Root,SizeHints.x,SizeHints.y,
				  SizeHints.width,SizeHints.height,
				  borderwidth,fore_pix,back_pix);
	Iconwin = XCreateSimpleWindow(Disp,Win,SizeHints.x,SizeHints.y,
				      SizeHints.width,SizeHints.height,
				      borderwidth,fore_pix,back_pix);	     



        classHint.res_name =  "asmixer";
        classHint.res_class = "ASMixer";
        XSetClassHint(Disp, Win, &classHint);        


	XSetWMNormalHints(Disp, Win, &SizeHints);
	XSelectInput(Disp, Win, (ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | 
				 StructureNotifyMask));
	XSelectInput(Disp, Iconwin, (ExposureMask | ButtonPressMask |  ButtonReleaseMask | PointerMotionMask | 
				     StructureNotifyMask));
	
	if (XStringListToTextProperty(&wname, 1, &name) ==0) 
	{
		fprintf(stderr, "asmixer: can't allocate window name\n");
		exit(-1);
	}
	XSetWMName(Disp, Win, &name);
	
	/* Create WinGC */
	gcm = GCForeground|GCBackground|GCGraphicsExposures;
	gcv.foreground = fore_pix;
	gcv.background = back_pix;
	gcv.graphics_exposures = False;
	WinGC = XCreateGC(Disp, Root, gcm, &gcv);  

	WmHints.initial_state = withdrawn?WithdrawnState:NormalState;

	WmHints.icon_window = Iconwin;
	WmHints.icon_x = SizeHints.x;
	WmHints.icon_y = SizeHints.y;

        WmHints.window_group = Win;
        WmHints.flags = StateHint | IconWindowHint | IconPositionHint
          | WindowGroupHint;



	XSetWMHints(Disp, Win, &WmHints); 	
	XMapWindow(Disp,Win);
	RedrawWindow();
}

/****************************************************************************/
#define MAX_LINE_NAME 16

void ParseCmdLine(int argc, char *argv[])
{
	struct {
	  char name[MAX_LINE_NAME];
	  int dev;
	} mixernames[SOUND_MIXER_NRDEVICES] = { 
		{ "VOLUME", SOUND_MIXER_VOLUME },
		{ "BASS", SOUND_MIXER_BASS },
		{ "TREBLE", SOUND_MIXER_TREBLE },
		{ "SYNTH", SOUND_MIXER_SYNTH },
		{ "PCM", SOUND_MIXER_PCM },
		{ "SPEAKER", SOUND_MIXER_SPEAKER },
		{ "LINE", SOUND_MIXER_LINE },
		{ "MIC", SOUND_MIXER_MIC },
		{ "CD", SOUND_MIXER_CD },
		{ "IMIX", SOUND_MIXER_IMIX },
		{ "ALTPCM", SOUND_MIXER_ALTPCM },
		{ "RECLEV", SOUND_MIXER_RECLEV },
		{ "IGAIN", SOUND_MIXER_IGAIN },
		{ "OGAIN", SOUND_MIXER_OGAIN },
		{ "LINE1", SOUND_MIXER_LINE1 },
		{ "LINE2", SOUND_MIXER_LINE2 },
		{ "LINE3", SOUND_MIXER_LINE3 }
	      };

	char *Argument;
	int i,j,CmdVol;
	char WhichLine[MAX_LINE_NAME];

	WhichLine[MAX_LINE_NAME] = 0;
	
	for(i = 1; i < argc; i++) 
	{
		Argument = argv[i];
		
		if (Argument[0] == '-') 
		{
			switch(Argument[1]) 
			{
                        case 'w': 
                                withdrawn=TRUE;
                                continue;

			case 'g': /* Geometry */
				if(++i >= argc) Help();
				Geometry = argv[i];
				continue;
			case 'd': /* Device */
				if(++i >= argc) Help();
				strcpy(&device[0], argv[i]);
				continue;

			case 't': /* Device */
				if(++i >= argc) Help();
				strcpy(&wintitle[0], argv[i]);
				continue;

			case '1': /* Mixer args */
			case '2':
			case '3':
				if(++i >= argc) Help();
				strncpy(WhichLine, argv[i], MAX_LINE_NAME-1);
				for(j = 0; j < SOUND_MIXER_NRDEVICES; j++)
				  {
				    if(strcasecmp(mixernames[j].name, WhichLine) == 0)
				      {
					voldev[Argument[1]-'1'] = mixernames[j].dev;
					break;
				      }
				  }
				continue;

			case 'v': /* Default volume By Alen Salamun*/
				if(++i >= argc) Help();
				CmdVol = 0;
				strncpy(WhichLine, argv[i], MAX_LINE_NAME-1);
				for(j=1;j<=strlen(WhichLine);j++)
				  CmdVol=CmdVol+(WhichLine[j-1]-48)*pow(10,strlen(WhichLine)-j);
				if (CmdVol>100)
				  CmdVol = 100;
				if (CmdVol <0)
				  CmdVol = 0;
				CmdVol=37-CmdVol*37/100;
				setvol_command(0,CmdVol);
				continue;


			case 'h':  /* Help */
				if(++i >= argc) Help();
				continue;
			
			 default:
				Help();
			}
		}
		else
			Help();
	}

}

/****************************************************************************/
void MainLoop()
{
	XEvent Event;            
			      
	/* Main loop */
	while(1)
	{		
		/* Check events */
		while (XPending(Disp))
		{
			XNextEvent(Disp,&Event);
			switch(Event.type)
			{		     
			 case Expose:		/* Redraw window */
				if(Event.xexpose.count == 0)
					{redraw=TRUE;
					 RedrawWindow(); } 
				break;		     
                         case MotionNotify:
                                if(mouseDown)
                                  setvol(Event.xbutton.x/16, Event.xmotion.y-4);
                                break;
                         case ButtonRelease:     /* Release */
                                mouseDown=FALSE;
                                break;
                         case ButtonPress:      /* Mouseclick */
                                mouseDown=TRUE;
                                setvol(Event.xbutton.x/16, Event.xbutton.y-4);
                                break;                                               
			 case DestroyNotify:	/* Destroy window */
				XFreeGC(Disp, WinGC);
				XDestroyWindow(Disp, Win);
				XDestroyWindow(Disp, Iconwin);
				XCloseDisplay(Disp);				
				exit(0); 
				break;			
			}
		
		XFlush(Disp);
		}
	usleep(250000L); 
	RedrawWindow();	
	}
}

/****************************************************************************/
void GetXPM(void)
{
	XWindowAttributes Attributes;
	int Ret;

	
	XGetWindowAttributes(Disp,Root,&Attributes);		

	buttonXPM.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
	Ret = XpmCreatePixmapFromData(Disp, Root, button, &buttonXPM.pixmap, 
				      &buttonXPM.mask, &buttonXPM.attributes);

        iconXPM.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
        Ret = XpmCreatePixmapFromData(Disp, Root, icon, &iconXPM.pixmap,
                                      &iconXPM.mask, &iconXPM.attributes);
      

	if(Ret != XpmSuccess)
        {
                fprintf(stderr, "ascd: not enough free color cells\n");
                exit(1);
        }          
}

/****************************************************************************/
int FlushExpose(Window w)
{
	XEvent dummy;
	int i=0;
	
	while (XCheckTypedWindowEvent (Disp, w, Expose, &dummy))i++;
	return i;
}

/****************************************************************************/
void RedrawWindow()
{	
        int x;

	getvol();

	
	if(redraw)
	{	


        redraw=FALSE;                                                                  

		if(changedmask)
		{
	       	 	XShapeCombineMask(Disp, Iconwin, ShapeBounding, 0, 0,
	                          iconXPM.mask, ShapeSet);   
			XShapeCombineMask(Disp, Win, ShapeBounding, 0,0,
				  iconXPM.mask, ShapeSet);
	
			for(x=0; x<3; x++)
				{
				XShapeCombineMask(Disp, Iconwin, ShapeBounding, 16*x, volbutloc[x],
					  buttonXPM.mask, ShapeUnion);
 				XShapeCombineMask(Disp, Win,     ShapeBounding, 16*x, volbutloc[x],
					  buttonXPM.mask, ShapeUnion);
				}
			changedmask=FALSE;
			redraw=TRUE;
			/* RedrawWindow(); */
		}

        XCopyArea(Disp,iconXPM.pixmap,Iconwin,WinGC,
                  0,0,iconXPM.attributes.width, iconXPM.attributes.height,0,0);
        XCopyArea(Disp,iconXPM.pixmap,Win,WinGC,
                  0,0,iconXPM.attributes.width, iconXPM.attributes.height,0,0);     

        for(x=0; x<3; x++)
                {
        XCopyArea(Disp,buttonXPM.pixmap,Iconwin,WinGC,
                  0,0,buttonXPM.attributes.width, buttonXPM.attributes.height,16*x,volbutloc[x]);
        XCopyArea(Disp,buttonXPM.pixmap,Win,    WinGC,
                  0,0,buttonXPM.attributes.width, buttonXPM.attributes.height,16*x,volbutloc[x]);
                }         
        
        XFlush(Disp);
	for(x=0;x<3;x++)
	  volold[x]=vol[x];
	}
}

/****************************************************************************/
Pixel GetColor(char *ColorName)
{
	XColor Color;
	XWindowAttributes Attributes;
	
	XGetWindowAttributes(Disp,Root,&Attributes);
	Color.pixel = 0;
	
	if (!XParseColor (Disp, Attributes.colormap, ColorName, &Color)) 
		fprintf(stderr,"ascd: can't parse %s\n", ColorName);
	else if(!XAllocColor (Disp, Attributes.colormap, &Color)) 
		fprintf(stderr,"ascd: can't allocate %s\n", ColorName);       
	
	return Color.pixel;
}
