/*****************************************************************************/
/*	astuner - AfterStep Tuner for RadioTrack and RadioReveal cards.      */
/*	Version 3.0						             */
/*	By David Muench.						     */		
/*	cc@spork.neonexus.com						     */
/*	http://www.neonexus.com/cc/					     */
/*									     */
/*	This is an 'AfterStep Look & Feel' Wharf style applet that can be    */
/*	used to control one of the before mentioned radio tuner cards.       */
/*									     */
/*   Based on ascd by Rob Malda and radiotrack-1.1 by Gideon J. le Grange.   */
/*****************************************************************************/

/********/

#include "astuner.h"

/* XPM struct and icons ******************************************************/

XpmIcon radiobg_onXPM, radiobg_offXPM, numbersXPM, radiobgXPM, numberstunedXPM, numbersuntunedXPM, lettersXPM, letterstunedXPM, lettersuntunedXPM;

#include "numbersuntuned.xpm"
#include "radiobg_on.xpm"
#include "radiobg_off.xpm"
#include "numberstuned.xpm"
#include "letterstuned.xpm"
#include "lettersuntuned.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( XpmIcon *v);
Pixel   GetColor(char *name);      
void	processipc(void);
int	checkstation(int curr);

/* Globals *******************************************************************/
int     lasttime=-1;
Display *Disp;
Window  Root;
Window  Iconwin;
Window  Win;
char    *Geometry = 0;

GC      WinGC;

int	currstation = 90500;
float	currstationf = 0.0;
char    radioon = 0;
int     err = 0;
int	used = 0;
int	presetcount = 0;
int	intune = FALSE;

uid_t	uid = 65535;
char	*ipcdir = 0;

int 	fd;
int	ioctl_retval;

char	letterson = FALSE;
char	onapreset = FALSE;

struct	timezone tz;
struct	timeval tv;
long	oldtime = 0;

struct DIR *dir;

/*****************************************************************************/
int main(int argc,char *argv[])
{
	int count;
	int ret = 0;
	struct passwd *pw;

	myname = argv[0];
	
	uid = getuid();
	pw = getpwuid(uid); 
	ipcdir = malloc(strlen(pw->pw_dir) + strlen(IPCDIR) + 1); 
	strcpy(ipcdir, pw->pw_dir); 
	strcat(ipcdir, IPCDIR);

	gettimeofday(&tv, &tz);
	oldtime = tv.tv_sec;

	ParseCmdLine(argc, argv); 

	yyin=fopen(RCFILE,"r");
	if (yyin==NULL)
	{
		printf("%s : Error opening configuration file %s\n",argv[0], RCFILE);
        	exit(-1);
	}
	yylex();                         
	fclose(yyin);

	currstationf = currstation;
	currstationf = currstationf / 1000;
	
	onapreset = checkstation(currstation);

	if((mkdir(ipcdir, 0700) != 0) && (errno != EEXIST))
	{
		printf("%s : Error creating directory %s", argv[0], ipcdir);
	}

	dir = opendir(ipcdir);

	if((fd = open(device, O_RDWR)) == -1)
	{
		printf("%s : Error opening radio device %s.", argv[0], device);
		exit(-1);
	}

        CreateWindow();
	MainLoop();

	close(fd);
	closedir(dir);
	free(ipcdir);
	return 0;
}

/*****************************************************************************/
void Help()
{       
	fprintf(stderr,"astuner - Version 3.0\n");
	fprintf(stderr,"Written by David Muench - cc@spork.neonexus.com\n");
	fprintf(stderr,"http://www.neonexus.com/cc/\n");
	fprintf(stderr,"usage:  astuner [-options ...] \n");
	fprintf(stderr,"options:\n");
	fprintf(stderr,"\n");       
	fprintf(stderr,"  Command                 Default   Why\n");
	fprintf(stderr,"g Geometry                none      Standard X Location\n");
	fprintf(stderr,"h Help                              Duh.\n");
	fprintf(stderr,"\n\n");
	exit(1); 
}

/****************************************************************************/
void CreateWindow(void)
{
	int i;
	unsigned int borderwidth ;
	char *display_name = NULL; 
	char *wname = "astuner";
	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;
	
	/* Open display */
	if (!(Disp = XOpenDisplay(display_name)))  
	{ 
		fprintf(stderr,"astuner: 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 = radiobgXPM.attributes.width;
	SizeHints.height= radiobgXPM.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);	     
	XSetWMNormalHints(Disp, Win, &SizeHints);
	XSelectInput(Disp, Win, (ExposureMask | ButtonPressMask | 
				 StructureNotifyMask));
	XSelectInput(Disp, Iconwin, (ExposureMask | ButtonPressMask | 
				     StructureNotifyMask));
	
	if (XStringListToTextProperty(&wname, 1, &name) ==0) 
	{
		fprintf(stderr, "astuner: 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);  

	 
		
		XShapeCombineMask(Disp, Win, ShapeBounding, 0, 0, 
				  radiobgXPM.mask, ShapeSet);
		XShapeCombineMask(Disp, Iconwin, ShapeBounding, 0, 0, 
				  radiobgXPM.mask, ShapeSet);
		
	WmHints.initial_state = NormalState;
	WmHints.icon_window = Iconwin;
	WmHints.icon_x = SizeHints.x;
	WmHints.icon_y = SizeHints.y;
	WmHints.flags = StateHint | IconWindowHint | IconPositionHint;
	XSetWMHints(Disp, Win, &WmHints); 	
	XMapWindow(Disp,Win);
	RedrawWindow(&radiobgXPM);
}

/****************************************************************************/
void ParseCmdLine(int argc, char *argv[])
{

	char *Argument;
	int i;       
	
	for(i = 1; i < argc; i++) 
	{
		Argument = argv[i];
		
		if (Argument[0] == '-') 
		{
			switch(Argument[1]) 
			{
			 case 'g': /* Geometry */
				if(++i >= argc) Help();
				Geometry = argv[i];
				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)
				{
					lasttime=01;
					XFlush(Disp);
					RedrawWindow(&radiobgXPM);
				} 
				break;		     
			 case ButtonPress:	/* Mouseclick */
				if((Event.xbutton.y >= 17) && (Event.xbutton.y <= 32) && (Event.xbutton.x <= 14))
				{
					if(!(currstationf <= RADIO_MINFREQ))
					{
						currstation = currstation - 200;
						currstationf = currstation;
						currstationf = currstationf / 1000;
						ioctl_retval = ioctl(fd, RADIO_FREQSET, RADIO_FCODE(currstationf));
						if(radioon == 0) radioon = 1;
						ioctl_retval = ioctl(fd, RADIO_ISTUNED, &intune);
						onapreset = checkstation(currstation);
					}
				}
				else if((Event.xbutton.y >= 17) && (Event.xbutton.y <= 32) && (Event.xbutton.x <= 31) && (Event.xbutton.x >= 18))
				{
					if(!(currstationf >= RADIO_MAXFREQ))
					{
						currstation = currstation + 200;
						currstationf = currstation;
						currstationf = currstationf / 1000;
						ioctl_retval = ioctl(fd, RADIO_FREQSET, RADIO_FCODE(currstationf));
						if(radioon == 0) radioon = 1;
						ioctl_retval = ioctl(fd, RADIO_ISTUNED, &intune);
						onapreset = checkstation(currstation);
					}
				}
				else if((Event.xbutton.y >= 34) && (Event.xbutton.y <= 48) && (Event.xbutton.x <= 22))
				{
					if(radioon == 0)
					{
						radioon = 1;
						ioctl_retval = ioctl(fd, RADIO_FREQSET, RADIO_FCODE(currstationf));
						ioctl_retval = ioctl(fd, RADIO_ISTUNED, &intune);
						onapreset = checkstation(currstation);
					}
					else
					{
						radioon = 0;
						ioctl_retval = ioctl(fd, RADIO_OFF);
						intune = FALSE;
					}
				}
				else if((Event.xbutton.y >= 34) && (Event.xbutton.y <= 48) && (Event.xbutton.x >= 27))
				{
					if(Event.xbutton.button == Button1)
					{
						if (presetcount >= used - 1)
							presetcount = 0;
						else
							presetcount++;
					}
					else if(Event.xbutton.button == Button2)
						presetcount = 0;
					else if(Event.xbutton.button == Button3)
					{
						if (presetcount == 0)
							presetcount = used - 1;
						else
							presetcount--;
					}
					currstation = stations[presetcount].freq;
					currstationf = currstation;
					currstationf = currstationf / 1000;
					ioctl_retval = ioctl(fd, RADIO_FREQSET, RADIO_FCODE(currstationf));
					if(radioon == 0) radioon = 1;
					ioctl_retval = ioctl(fd, RADIO_ISTUNED, &intune);
					onapreset = checkstation(currstation);
				}
				else if((Event.xbutton.y >= 17) && (Event.xbutton.y <= 24) && (Event.xbutton.x >= 36))
				{
					ioctl_retval = ioctl(fd, RADIO_VOLUP);
				}
				else if((Event.xbutton.y >= 26) && (Event.xbutton.y <= 32) && (Event.xbutton.x >= 36))
				{
					ioctl_retval = ioctl(fd, RADIO_VOLDOWN);
				}

				XFlush(Disp);
				RedrawWindow(&radiobgXPM);
				break;			 
			 case DestroyNotify:	/* Destroy window */
				XFreeGC(Disp, WinGC);
				XDestroyWindow(Disp, Win);
				XDestroyWindow(Disp, Iconwin);
				XCloseDisplay(Disp);				
				exit(0); 
				break;			
			}
		
		}
	if(onapreset == TRUE)
	{
		gettimeofday(&tv, &tz);
		if(oldtime + 4 <= tv.tv_sec)
		{
			oldtime = tv.tv_sec;
			if(letterson == TRUE)
				letterson = FALSE;
			else
				letterson = TRUE;
			XFlush(Disp);
			RedrawWindow(&radiobgXPM);
/*			printf("letterson = %d\n", letterson); */
		} 
	}
	processipc();
	usleep(50000L);
	}
}

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

	
	XGetWindowAttributes(Disp,Root,&Attributes);		
	radiobg_onXPM.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
	Ret = XpmCreatePixmapFromData(Disp, Root, radiobg_on, &radiobg_onXPM.pixmap, 
				      &radiobg_onXPM.mask, &radiobg_onXPM.attributes);

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

        numberstunedXPM.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
        Ret = XpmCreatePixmapFromData(Disp, Root, numberstuned, &numberstunedXPM.pixmap,
                                      &numberstunedXPM.mask, &numberstunedXPM.attributes);
      
        numbersuntunedXPM.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
        Ret = XpmCreatePixmapFromData(Disp, Root, numbersuntuned, &numbersuntunedXPM.pixmap,
                                      &numbersuntunedXPM.mask, &numbersuntunedXPM.attributes);

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

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

	radiobgXPM = radiobg_offXPM;
	numbersXPM = numbersuntunedXPM;
	lettersXPM = lettersuntunedXPM;
	if(Ret != XpmSuccess)
        {
                fprintf(stderr, "astuner: 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(XpmIcon *Icon)
{	
	int station[4];
	char ts[5];

	sprintf(ts, "%dl", currstation);
	if(currstation >= 100000)
	{
		station[0] = 1;
		station[1] = ts[1] - 48;
		station[2] = ts[2] - 48;
		station[3] = ts[3] - 48;
	}
	else
	{
		station[0] = 0;
		station[1] = ts[0] - 48;
		station[2] = ts[1] - 48;
		station[3] = ts[2] - 48;

	}

	FlushExpose(Win);

	if(radioon == 0) radiobgXPM = radiobg_offXPM;
	else radiobgXPM = radiobg_onXPM;
	
	if(intune == TRUE)
	{
		numbersXPM = numberstunedXPM;
		lettersXPM = letterstunedXPM;
	}
	else
	{
		numbersXPM = numbersuntunedXPM;
		lettersXPM = lettersuntunedXPM;
	}
	
	XShapeCombineMask(Disp, Win, ShapeBounding, 0, 0, 
			  radiobgXPM.mask, ShapeSet);

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

	if(letterson == FALSE)
	{
		if(currstation >= 100000)
		{
			XCopyArea(Disp, numbersXPM.pixmap, Win, WinGC,
				9*10+4,0, 6,11, 1,2);
		}
		else
		{
			XCopyArea(Disp, numbersXPM.pixmap, Win, WinGC,
				9*10+10,0, 6,11, 1,2);
		}
	
        	XCopyArea(Disp, numbersXPM.pixmap, Win, WinGC,
               		9*station[1],0, 9,11, 7,2);

	        XCopyArea(Disp, numbersXPM.pixmap, Win, WinGC,
        		9*station[2],0, 9,11, 16,2);

		XCopyArea(Disp, numbersXPM.pixmap, Win, WinGC,
			9*10,0, 4,11, 25,2);

	        XCopyArea(Disp, numbersXPM.pixmap, Win, WinGC,
        	        9*station[3],0, 9,11, 28,2);
	}
	else
	{
		XCopyArea(Disp, lettersXPM.pixmap, Win, WinGC,
			9 * (stations[presetcount].name[0] - 65),0, 9,11, 2,2);
		
		XCopyArea(Disp, lettersXPM.pixmap, Win, WinGC,
			9 * (stations[presetcount].name[1] - 65),0, 9,11, 11,2);
			
		XCopyArea(Disp, lettersXPM.pixmap, Win, WinGC,
			9 * (stations[presetcount].name[2] - 65),0, 4,11, 20,2);
		
		XCopyArea(Disp, lettersXPM.pixmap, Win, WinGC,
			9 * (stations[presetcount].name[3] - 65),0, 9,11, 29,2);
	}
}

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

/****************************************************************************/
void processipc(void)
{
	struct dirent *nextdir;
	char temp[500];
	char *command;
	
	rewinddir(dir);
	
	nextdir = readdir(dir);
	while(nextdir != NULL)
	{
		if(strcasecmp(nextdir->d_name, ".") == 0);
		else if(strcasecmp(nextdir->d_name, "..") == 0);
		else if(strncasecmp(nextdir->d_name, "ast-", 4) == 0)
		{
			command = rindex(nextdir->d_name, '-') + 1;

			if(strcasecmp(command, "off") == 0)
			{
				radioon = 0;
				ioctl_retval = ioctl(fd, RADIO_OFF);
				intune = FALSE;			
			}
			else if(strcasecmp(command, "on") == 0)
			{
				radioon = 1;
				currstationf = currstation;
				currstationf = currstationf / 1000;
				ioctl_retval = ioctl(fd, RADIO_FREQSET, RADIO_FCODE(currstationf));
				ioctl_retval = ioctl(fd, RADIO_ISTUNED, &intune);
				onapreset = checkstation(currstation);
			}
			else if(strncasecmp(command, "tune", 4) == 0)
			{
				if(strstr(command + 5, ".") != NULL)
				{
					currstation = atof(command + 5) * 1000;
					currstationf = currstation;
					currstationf = currstationf / 1000;
				}
				else
				{
					currstation = atol(command + 5);
					currstationf = currstation;
					currstationf = currstationf / 1000;
				}
				
				if(!(currstationf < RADIO_MINFREQ) && !(currstationf > RADIO_MAXFREQ))
				{
					ioctl_retval = ioctl(fd, RADIO_FREQSET, RADIO_FCODE(currstationf));
					if(radioon == 0) radioon = 1;
					ioctl_retval = ioctl(fd, RADIO_ISTUNED, &intune);
					onapreset = checkstation(currstation);
				}
			}
			else if(strcasecmp(command, "volup") == 0)
				ioctl_retval = ioctl(fd, RADIO_VOLUP);
			else if(strcasecmp(command, "voldown") == 0)
				ioctl_retval = ioctl(fd, RADIO_VOLDOWN);
			
			sprintf(temp, "%s/%s", ipcdir, nextdir->d_name);
			remove(temp);
			XFlush(Disp);
			RedrawWindow(&radiobgXPM);
		}
	
		nextdir = readdir(dir);
	}
}

int checkstation(int curr)
{
	int count;
	
	for(count = 0; count < used; count++)
	{
		if(curr == stations[count].freq)
			return TRUE;
	}
	letterson = FALSE;
	return FALSE;
}
