/***********************************************************************
 *   asbutton - AfterStep docable application launcher 
 *
 *   Originally based on wmbutton which was based on a bunch of other
 *   stuff.  Modified by Ryan Lathouwers <ryanlath@pacbell.net> and
 *   Jack O'Neill.
 *
 *   Ver 0 Rel 3    Jul 16, 1999
 *
 ***********************************************************************/
#include <Xlib.h>
#include <Xutil.h>
#include <xpm.h>
#include <extensions/shape.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "asbutton.h"

#include "backdrop.xpm"
#include "mask.xbm"
#include "./icons/asb9-1.xpm"
#include "./icons/asb9-2.xpm"
#include "./icons/asb9-3.xpm"
#include "./icons/asb9-4.xpm"
#include "./icons/asb9-5.xpm"
#include "./icons/asb9-6.xpm"
#include "./icons/asb9-7.xpm"
#include "./icons/asb9-8.xpm"
#include "./icons/asb9-9.xpm"
#include "./icons/asb4-1.xpm"
#include "./icons/asb4-2.xpm"
#include "./icons/asb4-3.xpm"
#include "./icons/asb4-4.xpm"

/***********************************************************************
 *	main
 ***********************************************************************/

int main(int argc, char **argv) 
{
	XEvent report;

	ASButtonSetup(argc, argv);

	while (1) {
	  	XNextEvent(display, &report);

	  	switch (report.type) {
	  		case Expose:
	    		if (report.xexpose.count != 0) 	
					break;
	    		if (verbose) 
					fprintf(stdout,"Event: Expose\n");	
	    		Redraw();
	    		break;      
	  		case ConfigureNotify:
	    		if (verbose) 
					fprintf(stdout,"Event: ConfigureNotify\n");	
	    		Redraw();
	    		break;
	  		case ButtonPress:
	    		switch (report.xbutton.button) {
	    			case Button1:
						button = WhichButton(report.xbutton.x, report.xbutton.y );
                  
						if (button >= 0 && button <= numb_of_apps)
	  						RunApp(button);
						
						if (verbose) 
	  						fprintf(stdout,"Button1:x=%d y=%d button=%d\n", 
		  					report.xbutton.x, report.xbutton.y, button);	
						break;
               case Button2:
						button = WhichButton(report.xbutton.x, report.xbutton.y );
						
                  if (button >= 0 && button <= numb_of_apps)
	  						RunApp(button + numb_of_apps);
                   
                  if (verbose) 
	  						fprintf(stdout,"Button2:x=%d y=%d button=%d\n", 
		  					report.xbutton.x, report.xbutton.y, button);	
                  break;
               case Button3:  
						button = WhichButton(report.xbutton.x, report.xbutton.y );
						
                  if (button >= 0 && button <= numb_of_apps)
	  						RunApp(button + numb_of_apps + numb_of_apps);
                     
                  if (verbose) 
	  						fprintf(stdout,"Button3:x=%d y=%d button=%d\n", 
		  					report.xbutton.x, report.xbutton.y, button);	
                  break;
	    		}
	    		break;
	  		case DestroyNotify:
	    		if (verbose) 
					fprintf(stdout, "Bye\n");
	    		XFreeGC(display, gc);
	    		XDestroyWindow(display,win);
	    		XDestroyWindow(display,iconwin);
	    		XCloseDisplay(display);
	    		exit(0);
	    		break;
	  	}
	}
	return (0);
}

/***********************************************************************
 *   ASButtonSetup
 *
 *   Parse command line and set up asbutton
 *
 ***********************************************************************/

void ASButtonSetup(int argc, char **argv)
{
	int i;
	char *home = NULL;
	
	/* Parse Command Line Arguments */  
	for (i = 1; i < argc; i++){
		if ( *argv[i] == '-' ) {
			switch (*(argv[i]+1)){
				case 'v':
					verbose = 1;
					break;
	    		case 'g':
					if (++i >= argc) 
						ShowUsage();
					sscanf(argv[i], "%s", geometry_str);
					if (verbose) 
						printf("Geometry is: %s\n", geometry_str);
					break;
	   	 	case 'd':
					if (++i >= argc) 
						ShowUsage();
					sscanf(argv[i], "%s", display_str);
					if (verbose) 
						printf("Display is: %s\n", display_str);
					break;
	    		case 'n':
					if (++i >= argc) 
						ShowUsage();
					sscanf(argv[i], "%d", &numb_of_apps);
					if (numb_of_apps != 9 && numb_of_apps != 4) 
						ShowUsage();
					if (verbose) 
						printf("Numb of Apps: %d\n", numb_of_apps);
					break;
	   		 case 'h':
					ShowUsage();
					break;
	    		case 'f':
					if (++i >= argc) 
						ShowUsage();
					sscanf(argv[i], "%s", configfile);
					if (verbose) 
						printf("Config File: %s\n", configfile);
					break;
	    	}
	  	}
	}

	if (strrchr(argv[0], (int)'/'))
		app_name = strrchr(argv[0], (int)'/') + 1;
	else
	  	app_name = argv[0];

	if (app_name == NULL)
		app_name = "asbutton";

	if (verbose)
		printf("App Name: %s\n", app_name);

	if (!strcmp(configfile, "")) {
	  	home = getenv("HOME");
	  	if( home != NULL ) {
	    	strcpy(configfile, home);
	    	strcat(configfile,"/.");
	  		strcat(configfile, app_name);
	  		strcat(configfile, "rc");
		} else {
	  		strcpy(configfile, ".");
	  		strcat(configfile, app_name);
	  		strcat(configfile, "rc");
		}
	}

	if ( (display = XOpenDisplay(display_str)) == NULL) {
	  fprintf(stderr,"Fail: XOpenDisplay for %s\n", display_str);	
	  exit(-1);
	}
	
	XSetup();

	/* Store the 'state' of the application for restarting */
	XSetCommand(display, win, argv, argc);	

	/* Rock and Roll */
	XMapWindow(display, win);
}

/***********************************************************************
 *   XSetup            
 *
 *   Initializes X Window stuff 
 *
 ***********************************************************************/

void XSetup(void)
{
	int dummy = 0;
	XSizeHints xsizehints;
	XWMHints  *xwmhints;
	XGCValues  xgcValues;	
	XTextProperty app_name_atom;

	screen  = DefaultScreen(display);
	rootwin = RootWindow(display, screen);
	depth   = DefaultDepth(display, screen);

	bg_pixel = WhitePixel(display, screen); 
	fg_pixel = BlackPixel(display, screen); 

	xsizehints.flags  = USSize | USPosition;
	xsizehints.width  = 64;
	xsizehints.height = 64;

	/* Parse Geometry string and fill in sizehints fields */
	XWMGeometry(display, screen, geometry_str, NULL, 0, 
	      &xsizehints, &xsizehints.x, &xsizehints.y,
	      &xsizehints.width, &xsizehints.height, &dummy);

	CreateWindow(&win, &rootwin, xsizehints);
	CreateWindow(&iconwin, &win, xsizehints); 

	/* Set up shaped windows */
	if ((pixmask = XCreateBitmapFromData(display, 
					 win, mask_bits,
					 mask_width, mask_height)) == 0) {
	  fprintf(stderr,"Fail: XCreateBitmapFromData\n");
	}
	
	XShapeCombineMask(display, win, ShapeBounding, 0, 0, pixmask, ShapeSet);
	XShapeCombineMask(display, iconwin, ShapeBounding, 0, 0, pixmask, ShapeSet);

	/* Convert in pixmaps from .xpm includes. */
	GetPixmaps();
	
	/* Interclient Communication stuff */
	/* Appicons don't work with out this stuff */
	xwmhints = XAllocWMHints();
	xwmhints->flags = WindowGroupHint | IconWindowHint | StateHint;	
	xwmhints->icon_window = iconwin;
	xwmhints->window_group = win;
	xwmhints->initial_state = WithdrawnState;  
	XSetWMHints(display, win, xwmhints);
	XSetWMNormalHints(display, win, &xsizehints);

	/* Tell window manager what the title bar name is. We never see */
	/* this anyways in the WithdrawnState      */
	if (XStringListToTextProperty(&app_name, 1, &app_name_atom) == 0) {
	  	fprintf(stderr,"%s: Can't set up window name\n", app_name);
	  	exit(-1);
	}
	XSetWMName(display, win, &app_name_atom);
	
	/* Create Graphic Context */	
	if (( gc = XCreateGC(display, win,(GCForeground | GCBackground), &xgcValues))
	     == NULL ) {
	  	fprintf(stderr,"Fail: XCreateGC\n");	
	  	exit(-1);
	}

	/* XEvent Masks. We want both window to process X events */
	XSelectInput(display, win, ExposureMask | ButtonPressMask | 
			PointerMotionMask | StructureNotifyMask );  
	XSelectInput(display, iconwin, ExposureMask | ButtonPressMask | 
			PointerMotionMask | StructureNotifyMask );
}

/***********************************************************************
 *   CreateWindow      
 *
 *   Create a window for XSetup()
 *
 ***********************************************************************/

void CreateWindow(Window *win, Window *rootwin, XSizeHints xsizehints)
{
	XClassHint xclasshint;
	*win = XCreateSimpleWindow(display, *rootwin,
				  xsizehints.x, xsizehints.y,
				  xsizehints.width, xsizehints.height,
				  0, fg_pixel, bg_pixel);
	xclasshint.res_name = "asbutton";
	xclasshint.res_class = "ASbutton";
	XSetClassHint(display, *win, &xclasshint);
}

/***********************************************************************
 *   Redraw
 *
 *	  Map the button region coordinates.
 *
 *   Draw the appropriate number of buttons on the 'visible' Pixmap 
 *   using data from the 'buttons' pixmap.
 *
 *   Then, copy the 'visible' pixmap to the two windows ( the withdrawn
 *   main window and the icon window which is the main window's icon image.)
 ***********************************************************************/

void Redraw() 
{
	int i, j, n;
	int dest_x, dest_y;
	int offset = 5;
	int button_size;
	int grid;

	if (verbose) 
		fprintf(stdout,"In Redraw()\n");	

	if(numb_of_apps == 9){
		grid = 3;
	  	button_size = 18;
	} else {
		grid = 2;
	  	button_size = 27;
	}

	XCopyArea(display, template.pixmap, visible.pixmap, gc, 0, 0,
	    template.attributes.width, template.attributes.height, 0, 0 ); 
 
	for (j = 0; j < grid; j++) {
	  	for (i = 0; i < grid; i++) {
	    	n = i + j * grid;
	    	dest_x = i * button_size + offset;
	    	dest_y = j * button_size + offset;
	    
	    	/* Define button mouse coords */
	    	button_region[n].x = dest_x;
	    	button_region[n].y = dest_y;
	    	button_region[n].i = dest_x + (button_size - 1);
	    	button_region[n].j = dest_y + (button_size - 1); 
	    
	    	/* Copy button images for valid apps -- move outside O loop*/
	      XCopyArea(display, buttons[n].pixmap, visible.pixmap, gc, 
	         0, 0, button_size, button_size, dest_x, dest_y);
	  	}
	}
	FlushExpose(win); 
	XCopyArea(display, visible.pixmap, win, gc, 0, 0,
	    visible.attributes.width, visible.attributes.height, 0, 0 ); 
	FlushExpose(iconwin); 
	XCopyArea(display, visible.pixmap, iconwin, gc, 0, 0,
	    visible.attributes.width, visible.attributes.height, 0, 0 );
}

/***********************************************************************
 *   WhichButton
 *
 *   Return the button at the x,y coordinates. 
 *   Return -1 if no button match.
 ***********************************************************************/

int WhichButton(int x, int y) 
{
	int index;

	for (index = 0; index < numb_of_apps; index++) 
	  	if ( x >= button_region[index].x &&
	 		  x <= button_region[index].i &&
	 		  y >= button_region[index].y &&
	 		  y <= button_region[index].j  ) 
	    	return( index + 1 );

	return(-1);
}

/***********************************************************************
 *   RunApp
 *
 *   Run the command given in the configuration file 'configfile'
 ***********************************************************************/

void RunApp(int app) 
{
	char *cmndstr;
	cmndstr = Parse(app);

	if (verbose){ 
		fprintf(stdout,"In *Parse()\n");	
		fprintf(stderr, "%s", cmndstr);
	}

	if (cmndstr != NULL) 
		system(cmndstr);

	free(cmndstr);
}

/***********************************************************************
 *   Parse
 *
 *   Parses the file 'configfile' for command to execute.
 ***********************************************************************/

char *Parse(int app) 
{
	FILE *fp;
	char *ptr  = NULL;
	char *cmnd = NULL;
	char *line = NULL;

	if (verbose) 
		fprintf(stdout,"In *Parse()\n");	

	if ((fp = fopen(configfile, "r")) == NULL) {
	  	fprintf(stderr, "%s: Configuration File not found\n", configfile);
	  	exit(-1);
	}

	do {
	  	line = ReadLine(fp);
	  	if (line == NULL) 
			break;
	  	if ((line[0] != '#') && (line[0] != '\n'))
	    	if (atoi(line) == app) 
				break;
	} while (1);

	if (line == NULL) {
		fclose(fp); 
		return(NULL);
	} else {
		ptr = line + 2;
		cmnd = malloc((strlen(ptr)+1)*sizeof(char));
		strcpy(cmnd, ptr);
		free(line);
		fclose(fp);
		return(cmnd);
	}
}

/***********************************************************************
 *   GetPixmaps
 *    
 *   Load XPM data into X Pixmaps.
 *  
 *   Pixmap template contains the untouched window backdrop image.
 *   Pixmap visible is the template pixmap with buttons drawn on it.
 *          -- what is seen by the user.
 *   Pixmap buttons holds the images for individual buttons that are
 *          later copied onto Pixmap visible.
 ***********************************************************************/

void GetPixmaps() 
{
	int i;
	char *filename;
	char **button_xpm = NULL;

	if (verbose) 
		fprintf(stdout,"In GetPixmaps()\n");	

	template.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
	visible.attributes.valuemask  |= (XpmReturnPixels | XpmReturnExtensions);

	for(i = 0; i < numb_of_apps; i++)
	  	buttons[i].attributes.valuemask  |= (XpmReturnPixels | XpmReturnExtensions);
	
	/* Template Pixmap. Never Drawn To. */
	filename = GetButtonFileName(-1);

	if (filename != NULL)
	  	if (XpmReadFileToPixmap( display, rootwin, filename,
	    	 &template.pixmap, &template.mask, 
	    	 &template.attributes) != XpmSuccess ){
	  		fprintf(stderr, "Can't Create 'template' Pixmap from %s, using default\n",
				 filename);
	    	free(filename);
	    	filename = NULL;
	  	} 

	/* Fall through to default xpm if create from file fails */
	if (filename == NULL)
		if ( XpmCreatePixmapFromData(	display, rootwin, backdrop_xpm,
			  &template.pixmap, &template.mask, 
			  &template.attributes) != XpmSuccess ) {
	    	fprintf(stderr, "Can't Create 'template' Pixmap\n");
	    	exit(1);
		}

	/* Visible Pixmap. Copied from template Pixmap and then drawn to. */
	if ( XpmCreatePixmapFromData(	display, rootwin, backdrop_xpm,
	     &visible.pixmap, &visible.mask, 
	     &visible.attributes) != XpmSuccess ) {
		fprintf(stderr, "Can't Create 'visible' Pixmap");
	   exit(1);
	}
	
	/* Button Pixmap.  */
	for(i = 0; i < numb_of_apps; i++){
		filename = GetButtonFileName(i + 1);
		if (filename != NULL) 
	    	if ( XpmReadFileToPixmap( display, rootwin, filename,
			     &buttons[i].pixmap, &buttons[i].mask,
			     &buttons[i].attributes) != XpmSuccess ) {
	    		fprintf(stderr, "Can't Create 'Button %d' Pixmap from %s, using default\n", i + 1, filename);
				free(filename);
				filename = NULL;
	  		}

		if (filename == NULL){
	  		switch(i){
				case 0: 
					if (numb_of_apps == 9)
				  		button_xpm = b1_xpm;
					else
				  		button_xpm = b1_2x2_xpm;
					break;
				case 1: 
					if (numb_of_apps == 9)
				  		button_xpm = b2_xpm;
					else
				  		button_xpm = b2_2x2_xpm;
					break;
				case 2: 
					if (numb_of_apps == 9)
				  		button_xpm = b3_xpm;
					else
				  		button_xpm = b3_2x2_xpm;
					break;
				case 3: 
					if (numb_of_apps == 9)
				  		button_xpm = b4_xpm;
					else
				  		button_xpm = b4_2x2_xpm;
					break;
	      	case 4: 
					button_xpm = b5_xpm;
					break;
	      	case 5: 
					button_xpm = b6_xpm;
					break;
	      	case 6: 
					button_xpm = b7_xpm;
					break;
	      	case 7: 
					button_xpm = b8_xpm;
					break;
	      	case 8: 
					button_xpm = b9_xpm;
					break;
	  		}

	    	if ( XpmCreatePixmapFromData(	display, rootwin, button_xpm,
				  &buttons[i].pixmap, &buttons[i].mask, 
				  &buttons[i].attributes) != XpmSuccess ) {
	      	fprintf(stderr, "Can't Create 'buttons' Pixmap");
	      	exit(1);
	    	}
		}
		if (filename != NULL)
			free(filename);
	}
}

/***********************************************************************
 *   GetButtonFileName
 *
 *   Parses the file 'configfile' for file names to use for buttons.
 *   Returns line beginning with ButtonX where X == position. 
 *   A position of -1 will search for a backdrop xpm.
 *   Not case sensitive for `Button' or `Backdrop'.
 ***********************************************************************/

char *GetButtonFileName(int position) 
{
	FILE *fp;
	char *ptr  = NULL;
	char *button_file = NULL;
	char *line = NULL;

	if ( verbose ) 
		fprintf(stdout,"In *GetButtonFileName()\n");	

	if ((fp = fopen(configfile, "r")) == NULL) {
	  	fprintf(stderr, "%s: Configuration File not found\n", configfile);
	  	exit(-1);
	}

	do {
	  	line = ReadLine(fp);
	  	if (line == NULL) 
			break;
	  	if ( (line[0] != '#') && (line[0] != '\n')){
	  		if (position == -1){
				if ( !strncasecmp(line, "backdrop", 8)) 
					break;
				continue;
	  		}
	    	if ( !strncasecmp(line, "button", 6))
				if (atoi(&(char)line[6]) == position) 
					break;
		}
	} while (1);

	if (line == NULL){
		fclose(fp);
		return(NULL);
	} else {
		ptr = line + 7;
		if (position == -1) 
			ptr++;
		while (isspace(*ptr)) 
			ptr++;

		/* Don't want the newline at the end, thank you. */
		ptr[strlen(ptr) - 1] = '\0';

		button_file = malloc((strlen(ptr)+1)*sizeof(char));
		strcpy(button_file, ptr);
		free(line);
		fclose(fp);
		return(button_file);
	}
}

/***********************************************************************
 *   FlushExpose
 ***********************************************************************/

void FlushExpose(Window w) 
{
	XEvent      dummy;

	while (XCheckTypedWindowEvent(display, w, Expose, &dummy));
}

/***********************************************************************
 *   ShowUsage
 ***********************************************************************/

void ShowUsage() 
{

	fprintf(stderr,"\n");
	fprintf(stderr,"asbutton-0.3\n");
	fprintf(stderr,"\n");
	fprintf(stderr,"usage: %s [-g geometry] [-d dpy] [-n buttons] [-v] -f [file]\n",app_name);
	fprintf(stderr,"\n");
	fprintf(stderr,"-g     geometry: ie: 64x64+10+10\n");
	fprintf(stderr,"-d     dpy:      Display. ie: 127.0.0.1:0.0\n"); 
	fprintf(stderr,"-n     buttons:  Number of buttons.  Must be 4 or 9.  Default is %d.\n",numb_of_apps);
	fprintf(stderr,"-f     file:     Full path to configuration file.\n");
	fprintf(stderr,"-v               Verbose Mode. \n");
	fprintf(stderr,"-h               Help. This screen.\n");
	fprintf(stderr,"\n");
	exit(-1);
}
