#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>
#include <time.h>
#include <math.h>
#include <X11/Xatom.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <linux/soundcard.h>

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


typedef struct volctrl
{
  int mixer_id;
  StereoVolume volume;
  int supported;
} VolumeControl;
 
VolumeControl Master;
int mixer_fd;

#include "volume.xpm"
/*#include "mask2.xbm"*/
#include "mark.xpm"

#define Center_X 24
#define Center_Y 26
#define Radius   13
#define min_a    0.75*M_PI
#define max_a    0.25*M_PI


int ONLYSHAPE=1;
int ICONIFIED=0; /* default is not iconified */


/* X11 Variablen *************************************************************/
Display *dpy;	  /* welches DISPLAY */
Window Root;      /* Hintergrund-Drawable */
int screen;

double Mark_Pos=M_PI/2;
int Pos=0;

int d_depth;
XSizeHints mysizehints;
XWMHints mywmhints;
Pixel back_pix, fore_pix;
GC NormalGC;
Window iconwin, win;       /* My home is my window */
char *ProgName;
char *Geometry;
char Execute[] = "echo no program has been specified >/dev/console";
char *ERR_colorcells = "not enough free color cells\n";

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

XpmIcon asMix,  Mark;
time_t actualtime; 

/* lokale Funktionen *********************************************************/
#define MW_EVENTS   (ExposureMask | ButtonPressMask | StructureNotifyMask | \
                      ButtonMotionMask | PointerMotionMask)
#define FALSE 0
void GetXPM(void);
Pixel GetColor(char *name);
void RedrawWindow( XpmIcon *v);

/*****************************************************************************/
/*****************************************************************************/
static char *help_message[] = {
"where options include:",
"    -exe <program>          program to start on click",
"    -position [+|-]x[+|-]y  position of asMix",
"    -shape                  without groundplate",
"    -iconic                 start up as icon",
NULL
};



void usage()
{
  char **cpp;

  fprintf(stderr,"usage:  %s [-options ...] \n", ProgName);
  for (cpp = help_message; *cpp; cpp++) {
    fprintf(stderr, "%s\n", *cpp);
  }
  fprintf(stderr,"\n");
  exit(1);
}

void UpdatePos(double a)
{
   XCopyArea(dpy,asMix.pixmap,win,NormalGC,
     Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
     Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2,
     Mark.attributes.width, Mark.attributes.height,
     Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
     Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
   XCopyArea(dpy,asMix.pixmap,iconwin,NormalGC,
     Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
     Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2,
     Mark.attributes.width, Mark.attributes.height,
     Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
     Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);

   Mark_Pos=a;

  Pos=Master.volume.left=Master.volume.right=(unsigned char)aToVol(Mark_Pos);

/*   fprintf(stderr,"set_slider: updating mixer %i to %i:%i\n",Master.mixer_id,Master.volume.left,Master.volume.right);
*/   
  if (ioctl(mixer_fd,MIXER_WRITE(Master.mixer_id),&Master.volume) == -1)
        fprintf(stderr,"Error writing mixer in Handle_slider");
 
   XCopyArea(dpy,Mark.pixmap,win,NormalGC,
     0,0,Mark.attributes.width, Mark.attributes.height,
     Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
     Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
   XCopyArea(dpy,Mark.pixmap,iconwin,NormalGC,
     0,0,Mark.attributes.width, Mark.attributes.height,
     Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
     Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
}

int aToVol(double a)
{
  double a2;
  int retval;
  
  if ((a<0) || (a<M_PI/2)) a2=a+2*M_PI; else a2=a;
  
  a2-=3*M_PI/4;
  
  retval=(int)(100*a2/(1.5*M_PI));
  if (retval<0) retval=0; else if (retval>100) retval=100;
  
  return retval; 
}

double VolToa(int p)
{
  double NewPos;
  
  NewPos=(float)p/100*3*M_PI/2+3*M_PI/4;
  if ((NewPos>M_PI)&&(NewPos<2*M_PI)) NewPos-=2*M_PI;

  return NewPos;
}

void MouseMove(int x, int y)
{
   double X,Y;
   double a;

   X= (double)x - Center_X;
   Y= (double)y - Center_Y;

   if (sqrt(X*X+Y*Y)<=2) return;
                
   a=atan2(Y,X);

   if ((a<min_a) &&(a>max_a)) {
      if ((Mark_Pos<-M_PI/2)||(Mark_Pos>M_PI/2)) a=min_a; else a=max_a;
   }

   UpdatePos(a);
}


static void sync_Control(VolumeControl *vcptr)
{
  int 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=(vcptr->volume.left+vcptr->volume.right)/2;
  
/*  fprintf(stderr,"%f : %i : %i\n",NewPos,Pos,portion);*/
  
  if (Pos!=portion) {
     UpdatePos(VolToa(portion));
     Pos=portion;
  }
}


int main(int argc,char *argv[])
{
  int i;
  unsigned int borderwidth ;
  char *display_name = NULL; 
  char *wname = "asMix";
  XGCValues gcv;
  unsigned long gcm;
  XEvent Event;
  XTextProperty name;
  XClassHint classHint;
//  Pixmap pixmask;
  ProgName = argv[0];
  Geometry = "";


  Master.mixer_id = SOUND_MIXER_VOLUME;
  
  mixer_fd = open ("/dev/mixer", O_RDWR, 0);   
  if (mixer_fd < 0) {
    fprintf (stderr,"Error opening mixer device");
    exit (1);
  }
        
  Master.supported=SOUND_MIXER_VOLUME;

  if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &Master.supported) == -1)
    Master.supported = 0xffff; /* Assume all are supported */
/*  printf("Master.supported = 0x%x\n",Master.supported);*/

  /* Parse command line options */
  ProgName = argv[0];
  

  for(i=1;i<argc;i++) {
    char *arg= argv[i];

    if (arg[0] == '-') {
      switch(arg[1]) {
      case 'e':
	if(++i >=argc) usage();
	strcpy(&Execute[0], argv[i]);
	strcat(&Execute[0], " &");
	continue;
      case 's':
	ONLYSHAPE=1;
	continue;
      case 'p':
	if(++i >=argc) usage();
	Geometry = argv[i];
	continue;
      case 'i':
	ICONIFIED=1;
	continue;
      default:
	usage();
      }
    }
  }

  /* Open the display */
  if (!(dpy = XOpenDisplay(display_name)))  
    { 
      fprintf(stderr,"asMix: can't open display %s\n", 
	      XDisplayName(display_name)); 
      exit (1); 
    } 
  screen= DefaultScreen(dpy);
  Root = RootWindow(dpy, screen);
  d_depth = DefaultDepth(dpy, screen);
  
  /* Icon Daten nach XImage konvertieren */
  GetXPM();
  
  /* Create a window to hold the banner */
  mysizehints.flags= USSize|USPosition;
  mysizehints.x = 0;
  mysizehints.y = 0;

  back_pix = GetColor("grey");
  fore_pix = GetColor("darkgrey");

  XWMGeometry(dpy, screen, Geometry, NULL, (borderwidth =1), &mysizehints,
	      &mysizehints.x,&mysizehints.y,&mysizehints.width,&mysizehints.height, &i); 

  mysizehints.width = asMix.attributes.width;
  mysizehints.height= asMix.attributes.height;

  win = XCreateSimpleWindow(dpy,Root,mysizehints.x,mysizehints.y,
			    mysizehints.width,mysizehints.height,
			    borderwidth,fore_pix,back_pix);
  iconwin = XCreateSimpleWindow(dpy,win,mysizehints.x,mysizehints.y,
				mysizehints.width,mysizehints.height,
				borderwidth,fore_pix,back_pix);


  /* Hints aktivieren */
  XSetWMNormalHints(dpy, win, &mysizehints);
  classHint.res_name =  "asMix";
  classHint.res_class = "asMix";
  XSetClassHint(dpy, win, &classHint);

  XSelectInput(dpy,win,MW_EVENTS);
  XSelectInput(dpy,iconwin,MW_EVENTS);
  
  if (XStringListToTextProperty(&wname, 1, &name) ==0) {
    fprintf(stderr, "asMix: can't allocate window name\n");
    exit(-1);
  }
  XSetWMName(dpy, win, &name);
  XSetWMName(dpy, iconwin, &name);
  
  /* Create a GC for drawing */
  gcm = GCForeground|GCBackground|GCGraphicsExposures;
  gcv.foreground = fore_pix;
  gcv.background = back_pix;
  gcv.graphics_exposures = FALSE;
  NormalGC = XCreateGC(dpy, Root, gcm, &gcv);  

  if (ONLYSHAPE) { /* try to make shaped window here */
//    pixmask = XCreateBitmapFromData(dpy, win, mask_bits, mask_width, mask_height);
    XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, /*pix*/asMix.mask, ShapeSet);
    XShapeCombineMask(dpy, iconwin, ShapeBounding, 0, 0, /*pix*/asMix.mask, ShapeSet);
  }
  
  mywmhints.initial_state = (ICONIFIED ? IconicState : NormalState);
  mywmhints.icon_window = iconwin;
  mywmhints.icon_x = mysizehints.x;
  mywmhints.icon_y = mysizehints.y;
  mywmhints.flags = StateHint | IconWindowHint | IconPositionHint;
  XSetWMHints(dpy, win, &mywmhints); 
  
  XSetWindowBackgroundPixmap(dpy,win,asMix.pixmap);
  XSetWindowBackgroundPixmap(dpy,iconwin,asMix.pixmap);


  sync_Control(&Master); 

  XMapWindow(dpy,win);

  RedrawWindow(&asMix);
  while(1)
    {
      if (actualtime != time(0))
	{
	  actualtime = time(0);
	  
          sync_Control(&Master); 
	  
	}
      
      /* read a packet */
      while (XPending(dpy))
	{
	  XNextEvent(dpy,&Event);
	  switch(Event.type)
	    {
	    case Expose:
	      if(Event.xexpose.count == 0 )
		RedrawWindow(&asMix);
	      break;
	    case ButtonPress:
              if (Event.xbutton.button == Button1) {
                  MouseMove(Event.xbutton.x, Event.xbutton.y);
               } else if (Event.xbutton.button == Button2) {
	           system(Execute);
	       }
                 
	      break;
	    case  MotionNotify: {
	        Window Root, Child;
	        int root_x, root_y;
	        int win_x, win_y;
	        unsigned int mask;
	       
                if (XQueryPointer(dpy, win, &Root, &Child,
                    &root_x, &root_y, &win_x, &win_y, &mask)!=0) {
                   if (mask & Button1MotionMask) 
                      MouseMove(Event.xbutton.x, Event.xbutton.y);
                 }
	        break;
	      }
	    case DestroyNotify:
              XFreeGC(dpy, NormalGC);
      XFlush(dpy);
/*              fprintf(stderr,"DestroyMe?\n");*/
/*              XDestroyWindow(dpy, win);
	      XDestroyWindow(dpy, iconwin);*/
              XCloseDisplay(dpy);
	      exit(0);
	    default:
	      break;      
	    }
	}
#ifdef SYSV
      poll((struct poll *) 0, (size_t) 0, 50);
#else
      usleep(50000L);			/* 50/100 sec */
#endif
    }
  return 0;
}
/****************************************************************************/
void nocolor(char *a, char *b)
{
 fprintf(stderr,"asMix: can't %s %s\n", a,b);
}
/****************************************************************************/
/* Konvertiere XPMIcons nach XImage */
void GetXPM(void)
{
  static char **bg_xpm;
  XColor col;
  XWindowAttributes attributes;
  int ret;

  bg_xpm =ONLYSHAPE ? volume_xpm : volume_xpm;

  /* for the colormap */
  XGetWindowAttributes(dpy,Root,&attributes);

  asMix.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
  ret = XpmCreatePixmapFromData(dpy, Root, bg_xpm, &asMix.pixmap, 
				&asMix.mask, &asMix.attributes);
  if(ret != XpmSuccess)
    {fprintf(stderr, ERR_colorcells);exit(1);}


  Mark.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
  ret = XpmCreatePixmapFromData(dpy, Root, mark_xpm, &Mark.pixmap, 
				&Mark.mask, &Mark.attributes);
  if(ret != XpmSuccess)
    {fprintf(stderr, ERR_colorcells);exit(1);}

}
/****************************************************************************/
/* Removes expose events for a specific window from the queue */
int flush_expose (Window w)
{
  XEvent dummy;
  int i=0;
  
  while (XCheckTypedWindowEvent (dpy, w, Expose, &dummy))i++;
  return i;
}

/****************************************************************************/
/* Draws the icon window */
void RedrawWindow( XpmIcon *v)
{
  flush_expose (iconwin);
  
    XCopyArea(dpy,Mark.pixmap,win,NormalGC,
              0,0,Mark.attributes.width, Mark.attributes.height,
              Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
              Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);

   XCopyArea(dpy,Mark.pixmap,iconwin,NormalGC,
              0,0,Mark.attributes.width, Mark.attributes.height,
              Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
              Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);

}
/****************************************************************************/
Pixel GetColor(char *name)
{
  XColor color;
  XWindowAttributes attributes;

  XGetWindowAttributes(dpy,Root,&attributes);
  color.pixel = 0;
   if (!XParseColor (dpy, attributes.colormap, name, &color)) 
     {
       nocolor("parse",name);
     }
   else if(!XAllocColor (dpy, attributes.colormap, &color)) 
     {
       nocolor("alloc",name);
     }
  return color.pixel;
}
