/* Program to change workspace, launch programs,
 *   and wait for windows to appear
 * Example:
 
 wksplaunch -wksp ws5 -wait 10 "xterm -bg red -title RedXTerm" "xterm -bg green -title GreenXTerm" -w RedXTerm -w GreenXTerm
 
 */

/* Programming notes:
 *
 * Cannot use -t as an option.  The X argument parser will take it as a title.
 *
 * DtWsmSetCurrentWorkspace does not work as described by the man page.
 *   Seems to always return !Success.
 *   Seems to need the DtWsmAddCurrentWorkspaceCallback and a timeout.
 *   Shouldn't need a widget, anyway.
 *
 */

#define WSM_WARNING_DISABLED

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <Xm/XmAll.h>
#include <Dt/Wsm.h>

/* Function prototypes */
void errmsg(char *fmt, ...);
int execute(char *s);
Window getwindowbyname(Display *dpy, Window parent, char *name);
int main(int argc, char **argv);
void timerproc(XtPointer clientdata, XtIntervalId id);
void usage(void);
Boolean workproc(caddr_t clientdata);
void workspacechangedcb(Widget w, XtPointer clientdata, XtPointer calldata);

/* Structures */
typedef struct {
    int found;
    char *title;
} WAITLIST;

/* Global variables */
char string[1024];

XtAppContext appcontext;
Display *display;
Window rootwindow;
int screen;

Widget toplevel,w;
XtWorkProcId workprocid=(XtWorkProcId)0;
XtIntervalId timerprocid=(XtIntervalId)0;

Arg args[25];
int nargs;

Atom *workspacelist=(Atom)0;
Atom workspaceatom;
XmString cstring;

char **cmdlist=(char **)0;
char *workspacename=(char *)0;
WAITLIST *waitlist=(WAITLIST *)0;

int nworkspaces=0,ncmds=0,ntitles=0,verbose=0;
unsigned long waittime=0;

/**************************** main ****************************************/
int main(int argc, char **argv)
{
    int i,ipass;

  /* Initialize toolkit  */
    nargs=0;
    XtSetArg(args[nargs],XmNallowShellResize,True);  nargs++;
    XtSetArg(args[nargs],XmNtitle,"WkspLaunch");  nargs++;
    toplevel=XtAppInitialize(&appcontext,"WkspLaunch","ControlApp",0,&argc,argv,
      NULL,args,nargs);
    display=XtDisplay(toplevel);
    screen=0;
    rootwindow=RootWindow(display,screen);

/* Parse arguments */
    if (argc < 2) {
	usage();
	exit(1);
    }
    for(ipass=0; ipass < 2; ipass++) {
	ntitles=ncmds=0;
	for (i=1; i < argc; i++) {
	    if (argv[i][0] == '-') {
		switch(argv[i][1]) {
		case 'w':
		    if(!strcmp(argv[i], "-wait")) {
			if(ipass) {
			    waittime=atol(argv[++i])*1000;
			} else ++i;
		    } else if(!strcmp(argv[i], "-wksp")) {
			if(ipass) {
			    workspacename=argv[++i];
			} else ++i;
		    } else {
			if(ipass) {
			    waitlist[ntitles].found=0;
			    waitlist[ntitles].title=argv[++i];
			} else   ++i;
			ntitles++;
		    }
		    break;
		case 'v':
		    verbose=1;
		    break;
		default:
		    errmsg("Invalid option %s\n",argv[i]);
		    exit(1);
		}
	    }
	    else {
		if(ipass) {
		    cmdlist[ncmds]=argv[i];
		}
		ncmds++;
	    }
	}
  /* Allocate space after first pass */
	if(ipass == 0) {
	    if(ncmds) {
		cmdlist=(char **)calloc(ncmds,sizeof(char *));
		if(!cmdlist) {
		    errmsg("Could not allocate space for commands");
		    exit(1);
		}
	    }
	    if(ntitles) {
		waitlist=(WAITLIST *)calloc(ntitles,sizeof(WAITLIST));
		if(!waitlist) {
		    errmsg("Could not allocate space for window titles");
		    exit(1);
		}
	    }
	}
    }

  /* Make label */
    nargs=0;
    cstring=XmStringCreateSimple("Big Brother is Watching!");
    XtSetArg(args[nargs],XmNlabelString,cstring); nargs++;
    XtSetArg(args[nargs],XmNmarginWidth,5); nargs++;
    XtSetArg(args[nargs],XmNmarginHeight,5); nargs++;
    XtSetArg(args[nargs],XmNtraversalOn,FALSE); nargs++;
    w=XmCreateLabel(toplevel,"label",args,nargs);
    XmStringFree(cstring);
    XtManageChild(w);

  /* Determine workspace atom */
    if(workspacename) {
	DtWsmWorkspaceInfo *wsinfo;
	char *atomname;
	
      /* Get workspace list */
	if (DtWsmGetWorkspaceList(display,rootwindow,&workspacelist,
	  &nworkspaces) == Success) {
	} else {
	    errmsg("Could not get workspace list");
	    exit(1);
	}
      /* Look in list for specified workspace name */
	for(i=0; i < nworkspaces; i++) {
	    DtWsmGetWorkspaceInfo(display,rootwindow,workspacelist[i],&wsinfo);
	    atomname=(char *)XmGetAtomName(display,wsinfo->workspace);
	    if(verbose)
	      printf("Workspace %2d: %s  Atom: (%d)\n",i,atomname,wsinfo->workspace);
	    if(!strcmp(workspacename,atomname)) {
		workspaceatom=wsinfo->workspace;
		DtWsmFreeWorkspaceInfo(wsinfo);
		break;
	    }
	    DtWsmFreeWorkspaceInfo(wsinfo);
	}
      /* Check if a matching workspace was found */
	if(!workspaceatom) {
	    sprintf(string,"Could not find a workspace that matches: %s\n"
	      "  Valid workspaces are\n",workspacename);
	    for(i=0; i < nworkspaces; i++) {
		atomname=(char *)XmGetAtomName(display,workspacelist[i]);
		strcat(string,"  ");
		strcat(string,atomname);
		strcat(string,"\n");
	    }
	    errmsg(string);
	    exit(1);
	}

    }

  /* Add work proc */
    workprocid=XtAppAddWorkProc(appcontext,workproc,NULL);

  /* Add timer proc */
    if(waittime) {
	timerprocid=XtAppAddTimeOut(appcontext,waittime,timerproc,NULL);
    }

  /* Add callback to determine if and when workspace changes */
    DtWsmAddCurrentWorkspaceCallback(toplevel,
      (DtWsmWsChangeProc)workspacechangedcb,NULL);

  /* Realize */
    XtRealizeWidget(toplevel);

  /* Main loop */
    XtAppMainLoop(appcontext);
    return(0);
}
/**************************** errmsg **************************************/
void errmsg(char *fmt, ...)
{
    va_list vargs;

    va_start(vargs,fmt);
    (void)fprintf(stderr,"*** ");
    (void)vfprintf(stderr,fmt,vargs);
    (void)fprintf(stderr,"\n");
    va_end(vargs);
}
/**************************** execute *************************************/
int execute(char *s)
/* From O'Reilly, Vol. 1, p. 438 */
{
    int status,pid,w;
    register void (*istat)(),(*qstat)();

    if((pid=fork()) == 0) {     /* Child */
	signal(SIGINT,SIG_DFL);
	signal(SIGQUIT,SIG_DFL);
	signal(SIGHUP,SIG_DFL);
	execl("/bin/sh","sh","-c",s,(char *)0);
	_exit(127);
    }
    istat=signal(SIGINT,SIG_IGN);
    qstat=signal(SIGQUIT,SIG_IGN);
    while((w=wait(&status)) != pid && w != -1) ;
    if(w == -1) status=-1;
    signal(SIGINT,istat);
    signal(SIGQUIT,qstat);
    return(status);
}
/**************************** getwindowbyname *****************************/
Window getwindowbyname(Display *dpy, Window parent, char *name)
  /* Based on a routine in xwininfo */
  /* Will only find the first window with that name */
{
    Window *children, dummy;
    unsigned int nchildren;
    int i;
    Window w=0;
    char *windowname;

  /* See if it is parent */
    if (XFetchName(dpy,parent,&windowname) && !strcmp(windowname, name))
      return(parent);

  /* Get the tree */
    if (!XQueryTree(dpy,parent,&dummy,&dummy,&children,&nchildren))
      return(0);
    
  /* Recursively look at the children */
    for (i=0; i < nchildren; i++) {
	w=getwindowbyname(dpy,children[i],name);
	if(w) break;     /* Found */
    }
    if (children) XFree ((char *)children);
    return(w);
}
/**************************** timerproc ***********************************/
void timerproc(XtPointer clientdata, XtIntervalId id)
{
    errmsg("Timed out after %ld sec\n",waittime/1000);
    exit(3);
}
/**************************** usage ***************************************/
void usage(void)
{
    printf("\nUsage: wksplaunch [options] [cmd [cmd ...]]\n");
    printf("  Options:\n");
    printf("    -w title      Wait for window with title \"title\" to appear.\n");
    printf("                   (This option may be repeated with different titles.)\n");
    printf("    -q            Quiet.\n");
    printf("    -wait time    Timeout after time seconds.\n");
    printf("    -wksp name    First change to workspace named \"name\".\n");
    printf("                   (Valid names are typically ws0, ws1, ws2, ....)\n");
    printf("\n");
    printf("The program will first switch to the specified workspace if one is\n");
    printf("given, then execute the commands if any are given, then wait for the\n");
    printf("specified time for the windows with the specified titles to appear if\n");
    printf("a time and titles are specified.  It will return 0 on success, 1 if\n");
    printf("the workspace could not be changed, 2 if the commands could not be\n");
    printf("started, and 3 if it times out without detecting the named windows.\n");
    printf("\n");
    printf("The order of the commands and options is not important.  An \"&\" will\n");
    printf("be attached to the commands.  You do not have to do it yourself.\n");
    printf("\n");
    printf("If there are duplicate titles on the server, the program will only look\n");
    printf("for the first match.  The program only returns 2 if the commands cannot\n");
    printf("be started.  They may still fail.\n");
    printf("\n");
}
/**************************** workproc ************************************/
Boolean workproc(caddr_t clientdata)
{
    Atom wsatom;
    int i,found,len;
    static int count=0,docommands=1,nfound=0,occupy=0;
    char *cmd,*newcmd,*title;

  /* Set to occupy all workspaces */
    if(!occupy) {
	DtWsmOccupyAllWorkspaces(display,XtWindow(toplevel));
	occupy=1;
    }
    
  /* Change workspace if it needs to be */
    if(workspacename) {
	count++;
      /* Find out the current workspace */
	if(DtWsmGetCurrentWorkspace(display,rootwindow,&wsatom) != Success) {
	    errmsg("Could not determine current workspace");
	    exit(1);
	}
      /* If it is not the current one change it */
	if(workspaceatom == wsatom) {
	    workspacename=(char *)0;
	} else {
	    if(DtWsmSetCurrentWorkspace(toplevel,workspaceatom) != Success) {
#ifdef DEBUG		
		errmsg("Try %d: Could not set workspace to: %s (%d)",
		  count,workspacename,workspaceatom);
#endif		
	      /* Set it to be called again until the workspace-changed callback
	       * sees that it is changed */
		return 0;
	    }
	}
    }

  /* Workspace is OK, execute commands */
    if(docommands) {
	for(i=0; i < ncmds; i++) {
	    cmd=cmdlist[i];
	    len=strlen(cmd);
	    newcmd=(char *)calloc(len+2,sizeof(char));
	    if(!newcmd) {
		errmsg("Could not allocate memory for commands");
		exit(2);
	    }
	    sprintf(newcmd,"%s &",cmd);
	    if(verbose) printf("Executing [%d]: %s\n",i,newcmd);
	    XFlush(display);
	    if(execute(newcmd)) {
		errmsg("Could not execute: \"%s\"",newcmd);
		free(newcmd);
		exit(2);
	    }
	    free(newcmd);
	}
	docommands=0;
    }

  /* Exit if no wait time specified */
    if(!waittime) exit(0);        

  /* Check for titles */
    for(i=0; i < ntitles; i++) {
	title=waitlist[i].title;
	found=waitlist[i].found;
	if(!found) {
	    if(verbose)
	      printf("Looking for title[%d]: %s\n",i,title);
	    if(getwindowbyname(display,rootwindow,title)) {
		waitlist[i].found=1;
		nfound++;
	    }
	}
    }

  /* Set to be called again depending on how many found */
    if(nfound == ntitles) {
	if(verbose)
	  printf("All window titles found\n");
	exit(0);
    } else {
	return 0;
    }
}
/**************************** workspacechangedcb **************************/
void workspacechangedcb(Widget w, XtPointer clientdata, XtPointer calldata) 
{
    DtWsmWorkspaceInfo *wsinfo;
    Atom wsatom;
    char *atomname;
    
  /* Find out the current workspace */
    if(DtWsmGetCurrentWorkspace(display,rootwindow,&wsatom) != Success) {
	errmsg("Could not determine workspace after it changed");
	return;
    }
  /* Find its name */
    if(DtWsmGetWorkspaceInfo(display,rootwindow,wsatom,&wsinfo) != Success) {
	errmsg("Could not get workspace name after it changed (Not Fatal)");
	return;
    } else {
	atomname=(char *)XmGetAtomName(display,wsinfo->workspace);
	DtWsmFreeWorkspaceInfo(wsinfo);
	if(verbose)
	  printf("Workspace changed to: %s  Atom: (%d)\n",
	    atomname,wsinfo->workspace);
    }
  /* Keep it from changing again if it is the right one */
    if(workspaceatom == wsatom);
    workspacename=(char *)0;
}
