/*rootmenu.c- user defined menu
 * 
 *  WindowMaker window manager
 * 
 *  Copyright (c) 1997 Alfredo K. Kojima
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "wconfig.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <time.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#include "generic/wwmlib.h"
#include "WindowMaker.h"
#include "actions.h"
#include "menu.h"
#include "funcs.h" 
#include "dialog.h"
#include "keybind.h"
#include "stacking.h"

extern char *Locale;

extern char *MenuConfigPath;
extern wCursor[WCUR_LAST];

extern Time LastTimestamp;

extern WPreferences wPreferences;

extern WShortKey *wKeyBindings[WKBD_LAST];


static char*expandOptions(WScreen *scr, char *cmdline);


/*
 * Syntax:
 * # main menu
 * "Menu Name" MENU
 * 	"Title" EXEC command_to_exec -params
 * 	"Submenu" MENU
 * 		"Title" EXEC command_to_exec -params
 * 	"Submenu" END
 * 	"Workspaces" WORKSPACE_MENU
 * 	"Title" built_in_command
 * 	"Quit" EXIT
 * 	"Quick Quit" EXIT QUICK
 * "Menu Name" END
 * 
 * Built-in commands:
 * 
 * SHUTDOWN [QUICK] - closes the X server [without confirmation]
 * REFRESH - forces the desktop to be repainted
 * EXIT [QUICK] - exit the window manager [without confirmation]
 * EXEC <program> - execute an external program
 * WORKSPACE_MENU - places the workspace submenu
 * ARRANGE_ICONS
 * RESTART [<window manager>] - restarts the window manager
 * SHOW_ALL - unhide all windows on workspace
 * HIDE_OTHERS - hides all windows excep the focused one
 * 
 * Temporary: 
 * RENAME_WS <new name>- rename current workspace
 */


#define M_QUICK		1

/* menu commands */

static void
execCommand(WMenu *menu, WMenuEntry *entry)
{
    char *cmdline;

    cmdline = expandOptions(menu->frame->screen_ptr,  
			    (char*)entry->clientdata);
    XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
		 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT],
		 CurrentTime);
    XSync(dpy, 0);

    if (cmdline) {
	if (fork()==0) {
	    execl("/bin/sh", "/bin/sh", "-c", cmdline, NULL);
	    exit(-1);
	}
	free(cmdline);
    }
    XUngrabPointer(dpy, CurrentTime);
    XSync(dpy, 0);
}

static void
exitCommand(WMenu *menu, WMenuEntry *entry)
{
    /* prevent reentrant calls */
    wMenuSetEnabled(menu, entry->order, False);
    if ((int)entry->clientdata==M_QUICK
	|| wMessageDialog(menu->frame->screen_ptr, _("Exit"),
		_("Exit window manager?"), WD_EXIT_CONFIRM)==WDB_EXIT) {
#ifdef DEBUG
	printf("Exiting WindowMaker.\n");
#endif

	wScreenSaveState(menu->frame->screen_ptr);
	
	RestoreDesktop(menu->frame->screen_ptr);
	exit(0);
    }
    wMenuSetEnabled(menu, entry->order, True);
}


static void
shutdownCommand(WMenu *menu, WMenuEntry *entry)
{
    /* prevent reentrant calls */
    wMenuSetEnabled(menu, entry->order, False);
    if ((int)entry->clientdata==M_QUICK
	|| wMessageDialog(menu->frame->screen_ptr, _("Close X session"), 
	  _("Close Window System session?\n(all applications will be closed)"),
		       WD_EXIT_CONFIRM)==WDB_EXIT) {
	printf(_("Exiting...\n"));
	
	wScreenSaveState(menu->frame->screen_ptr);

 	WipeDesktop(menu->frame->screen_ptr);
	exit(0);
    }
    wMenuSetEnabled(menu, entry->order, True);
}


static void
restartCommand(WMenu *menu, WMenuEntry *entry)
{
    wScreenSaveState(menu->frame->screen_ptr);

    RestoreDesktop(menu->frame->screen_ptr);
    Restart((char*)entry->clientdata);
}


static void
refreshCommand(WMenu *menu, WMenuEntry *entry)
{
    wRefreshDesktop(menu->frame->screen_ptr);
}


static void
arrangeIconsCommand(WMenu *menu, WMenuEntry *entry)
{
    wArrangeIcons(menu->frame->screen_ptr);
}

static void
showAllCommand(WMenu *menu, WMenuEntry *entry)
{
    wShowAllWindows(menu->frame->screen_ptr);
}

static void
hideOthersCommand(WMenu *menu, WMenuEntry *entry)
{
    wHideOtherApplications(menu->frame->screen_ptr->focused_window);
}

static void
switchWSCommand(WMenu *menu, WMenuEntry *entry)
{
    wMenuSetEnabled(menu, menu->frame->screen_ptr->workspace+2, True);
    wChangeWorkspace(menu->frame->screen_ptr, (int)entry->clientdata-1);
    wMenuSetEnabled(menu, menu->frame->screen_ptr->workspace+2, False);
    
    /* don't let user destroy current workspace */
    if (menu->frame->screen_ptr->workspace 
	== menu->frame->screen_ptr->last_workspace) {
	wMenuSetEnabled(menu, 1, False);
    } else {
	wMenuSetEnabled(menu, 1, True);
    }
}


static void
newWSCommand(WMenu *menu, WMenuEntry *foo)
{
    char title[MAX_MENU_TEXT_LENGTH+6];
    int ws;
    WMenuEntry *entry;

    wMenuSetEnabled(menu, menu->frame->screen_ptr->workspace+2, True);
    ws = wNewWorkspace(menu->frame->screen_ptr);
    if (ws<0) {
	wMenuSetEnabled(menu, menu->frame->screen_ptr->workspace+2, False);
	wMenuSetEnabled(menu, 0, False);
	return;
    }
    if (ws>=MAX_WORKSPACES) {
	wMenuSetEnabled(menu, 0, False);
    }
    sprintf(title, _("Switch to %i"), ws+1);
    entry = wMenuAddCallback(menu, title, switchWSCommand, (void*)(ws+1));
    if (ws<9) {
	int kcode;
	if (wKeyBindings[WKBD_WORKSPACE1+ws]) {
	    kcode = wKeyBindings[WKBD_WORKSPACE1+ws]->keycode;
	    entry->rtext = 
	      strdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)));
	}
    }
    wMenuRealize(menu);
    /* disable current workspace entry */
    wMenuSetEnabled(menu, menu->frame->screen_ptr->workspace+2, False);
    wMenuPaint(menu);
    /* disable destroy last entry */
    wMenuSetEnabled(menu, 1, False);
}


static void
renameWSCommand(WMenu *menu, WMenuEntry *entry)
{
    WMenuEntry *wentry;
    WScreen *scr=menu->frame->screen_ptr;
    char *name, *sname;
    
    if (scr->workspace_menu) {
	wentry = scr->workspace_menu->entries[scr->workspace+2];
    } else {
	return;
    }
    name = expandOptions(scr, (char*)entry->clientdata);
    if (!name)
      return;
    if (strlen(name)>MAX_WORKSPACENAME_WIDTH)
	name[MAX_WORKSPACENAME_WIDTH]=0;
    sname = wmalloc(strlen(name)+strlen(_("Switch to %s"))+1);
    free(wentry->text);
    sprintf(sname, _("Switch to %s"), name);
    wentry->text = sname;
    wMenuRealize(scr->workspace_menu);
    wMenuPaint(scr->workspace_menu);
}

static void
deleteWSCommand(WMenu *menu, WMenuEntry *entry)
{
    int i;
    
    i = menu->frame->screen_ptr->last_workspace;
    wDeleteLastWorkspace(menu->frame->screen_ptr);
    if (i!=menu->frame->screen_ptr->last_workspace) {
	wMenuRemoveItem(menu, i+2);
	wMenuRealize(menu);
	wMenuPaint(menu);
    }
    /* don't let user destroy current workspace */
    if (menu->frame->screen_ptr->workspace 
	== menu->frame->screen_ptr->last_workspace) {
	wMenuSetEnabled(menu, 1, False);
    } else {
	wMenuSetEnabled(menu, 1, True);
    }
}

/*******************************/


static void
addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
{
    char wstitle[MAX_MENU_TEXT_LENGTH+6];
    WMenu *wsmenu;
    WMenuEntry *entry;
    int i;

    wsmenu = wMenuCreate(scr, _("Workspaces"), False);
    if (!wsmenu) {
	wWarning(_("could not create Workspace menu"));
	return;
    }
    wMenuAddCallback(wsmenu, _("New"), newWSCommand, NULL);
    wMenuAddCallback(wsmenu, _("Destroy Last"), deleteWSCommand, NULL);
    if (scr->last_workspace>=MAX_WORKSPACES) {
	wMenuSetEnabled(wsmenu, 0, False);
    }
    for (i=0; i<=scr->last_workspace; i++) {
	sprintf(wstitle, _("Switch to %i"), i+1);
	entry = wMenuAddCallback(wsmenu, wstitle, switchWSCommand, 
				 (void*)(i+1));
	if (i<10) {
	    int kcode;
	    if (wKeyBindings[WKBD_WORKSPACE1+i]) {
		kcode = wKeyBindings[WKBD_WORKSPACE1+i]->keycode;
		entry->rtext = 
		  strdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)));
	    }
	}
    }
    wMenuSetEnabled(wsmenu, scr->workspace+2, False);
    wMenuSetEnabled(wsmenu, 1, False);
    wMenuEntrySetCascade(menu, wMenuAddCallback(menu, title, NULL, NULL), 
			 wsmenu);
    scr->workspace_menu = wsmenu;    
}


static WMenu*
parseCascade(WScreen *scr, WMenu *menu, FILE *file)
{
    char linebuf[MAXLINE];
    char elinebuf[MAXLINE];
    char title[MAXLINE];
    char command[MAXLINE];
    char params[MAXLINE];
    char *line;

    while (!feof(file)) {
	int lsize, ok, parts;
	
	ok=0;
	fgets(linebuf, MAXLINE, file);
	line = cropline(linebuf);
	lsize = strlen(line);
	while (!ok && !feof(file)) {
	    if (line[lsize-2]=='\\') {
		char *line2;
		int lsize2;
		fgets(elinebuf, MAXLINE, file);
		line2=cropline(elinebuf);
		lsize2=strlen(line2);
		if (lsize2+lsize>MAXLINE) {
		    wWarning(_("maximal line size exceeded in menu config: %s"),
			     line);
		    ok=2;
		} else {
		    line[lsize-2]=0;
		    lsize+=lsize2-2;
		    strcat(line, line2);
		}
	    } else {
		ok=1;
	    }
	}
	if (ok==2)
	  continue;
	
	if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/')) 
	  continue;
	
	if ((parts=separateline(line, title, command, params)) < 2) {
	    wWarning(_("missing command in menu config: %s"), line);
	    goto error;
	}
	if (strcmp(command, "MENU")==0) {
	    WMenu *cascade;

	    cascade = wMenuCreate(scr, title, False);
	    if (parseCascade(scr, cascade, file)==NULL) {
		wMenuDestroy(cascade, True);
	    } else {
		wMenuEntrySetCascade(menu, 
				     wMenuAddCallback(menu, title, NULL, NULL),
				     cascade);
	    }
	} else {
	    /* see if the menu has ended */
	    if (strcmp(command, "END")==0) {
		return menu;
	    }
	    /* add new item */
	    if (strcmp(command, "EXEC")==0) {
		WMenuEntry *entry;
		if (parts<3)
		  wWarning(_("missing parameter for menu command \"%s\""),
			   command);
		else {
		    entry = wMenuAddCallback(menu, title, execCommand, 
					     strdup(params));
		    entry->free_cdata = free;
		}
	    } else if (strcmp(command, "EXIT")==0) {

		if (strcmp(params, "QUICK")==0)
		  wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
		else
		  wMenuAddCallback(menu, title, exitCommand, NULL);

	    } else if (strcmp(command, "SHUTDOWN")==0) {

		if (strcmp(params, "QUICK")==0)
		  wMenuAddCallback(menu, title, shutdownCommand, (void*)M_QUICK);
		else
		  wMenuAddCallback(menu, title, shutdownCommand, NULL);
		
	    } else if (strcmp(command, "REFRESH")==0) {
		wMenuAddCallback(menu, title, refreshCommand, NULL);
		
	    } else if (strcmp(command, "WORKSPACE_MENU")==0) {
		if (scr->workspace_menu)
		  wWarning(_("menu can only have one WORKSPACE_MENU"));
		else
		  addWorkspaceMenu(scr, menu, title);
	    } else if (strcmp(command, "ARRANGE_ICONS")==0) {
		wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
	    } else if (strcmp(command, "HIDE_OTHERS")==0) {
		wMenuAddCallback(menu, title, hideOthersCommand, NULL);
	    } else if (strcmp(command, "SHOW_ALL")==0) {
		wMenuAddCallback(menu, title, showAllCommand, NULL);
	    } else if (strcmp(command, "RESTART")==0) {
		WMenuEntry *entry;

		entry = wMenuAddCallback(menu, title, restartCommand, 
					 strdup(params));
		entry->free_cdata = free;
	    } else if (strcmp(command, "RENAME_WS")==0) {
		WMenuEntry *entry;
		if (parts<3)
		  wWarning(_("missing parameter for menu command \"%s\""),
			   command);
		else {
		    entry = wMenuAddCallback(menu, title, renameWSCommand, 
					     strdup(params));
		    entry->free_cdata = free;
		}
	    } else {
		wWarning(_("unknown command \"%s\" in menu config."), command);
	    }
	}
    }
    wWarning(_("syntax error in menu config. END declaration missing"));
    return menu;

    error:
    return NULL;
}


static WMenu*
parseMenu(WScreen *scr, FILE *file)
{
    WMenu *menu=NULL;
    char linebuf[MAXLINE];
    char title[MAXLINE];
    char command[MAXLINE];
    char params[MAXLINE];
    char *line;
    
    while (!feof(file)) {
	fgets(linebuf, MAXLINE, file);
	line = cropline(linebuf);
	if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/')) 
	  continue;

	if (separateline(line, title, command, params) < 2) {
	    wWarning(_("syntax error in menu config. %s"), line);
	    return NULL;
	}
	if (strcasecmp(command, "MENU")==0) {
	    menu = wMenuCreate(scr, title, True);
	    return parseCascade(scr, menu, file);
	} else {
	    wWarning(_("no title given for the root menu"));
	    return NULL;
	}
    }
    return NULL;
}

static WMenu *
makeDefaultMenu(WScreen *scr)
{
    WMenu *menu=NULL;
    
    menu = wMenuCreate(scr, _("Commands"), True);
    wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
    return menu;
}


static void
raiseMenus(WMenu *menu)
{
    int i;
    
    if (menu->flags.mapped) {
	wRaiseFrame(menu->frame);
    }
    for (i=0; i<menu->cascade_no; i++) {
	if (menu->cascades[i])
	  raiseMenus(menu->cascades[i]);
    }
}



void 
RefreshWorkspaceMenu(WScreen *scr)
{
    WMenu *menu = scr->workspace_menu;
    char title[MAX_MENU_TEXT_LENGTH+6];
    int i;

    if (!menu)
      return;

    for (i=0; i<=scr->last_workspace+2; i++) 
      wMenuSetEnabled(menu, i, True);
    if (menu->entry_no-2<=scr->last_workspace) {
	WMenuEntry *entry;

	for (i=menu->entry_no-2; i<=scr->last_workspace; i++) {
	    sprintf(title, _("Switch to %i"), i+1);
	    entry = wMenuAddCallback(menu, title, switchWSCommand, 
				     (void*)(i+1));
	    if (i<10) {
		int kcode;
		if (wKeyBindings[WKBD_WORKSPACE1+i]) {
		    kcode = wKeyBindings[WKBD_WORKSPACE1+i]->keycode;
		    entry->rtext = 
		      strdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)));
		}
	    }
	}
	wMenuRealize(menu);
	/* disable current workspace entry */
	wMenuSetEnabled(menu, scr->workspace+2, False);
	wMenuPaint(menu);
	/* disable "destroy last" entry */
	wMenuSetEnabled(menu, 1, False);
    } else {
	wMenuSetEnabled(menu, scr->workspace+2, False);
    }
    wMenuSetEnabled(menu, 0, True);
    wMenuSetEnabled(menu, 1, (scr->workspace!=scr->last_workspace));
}

/*
 *---------------------------------------------------------------------- 
 * OpenRootMenu--
 * 	Opens the root menu, parsing the menu configuration file if it
 * wasn't already or the config file was changed.
 *	If the menu is already mapped and is not sticked to the
 * root window, it will be unmapped.
 * 
 * Side effects:
 * 	The menu may be remade.
 * 
 * Notes:
 * 	If the menu was never mapped or the configuration file was 
 * changed since the last mapping the menu will be reconstructed. 
 * If the menu reconstruction fails, the previous menu will be used.
 *---------------------------------------------------------------------- 
 */
void
OpenRootMenu(WScreen *scr, int x, int y)
{
    WMenu *menu=NULL;
    FILE *file=NULL;
    struct stat stats;
    static time_t last_changed=0;
#ifdef USECPP
    char command[MAXLINE], *args;
    int cpp=0;
#endif

    if (scr->root_menu && scr->root_menu->flags.mapped) {
	menu = scr->root_menu;
	if (!menu->flags.buttoned) {
	    wMenuUnmap(menu);
	} else {
	    wRaiseFrame(menu->frame);
	    wMenuMapAt(menu->brother, x-menu->frame->width/2, 
		       y-menu->titlebar->height/2);
	}
	return;
    }
    
    if (!MenuConfigPath) {
	char menu_file[MAXPATH];
	if (Locale) {
	    int i;
	    /* localized menu config file */	    
	    strcpy(menu_file, DEF_MENUCONFIG);
	    strcat(menu_file, ".");
	    strcat(menu_file, Locale);
	    MenuConfigPath = find_file(DEF_CONFIG_PATHS, menu_file);
	    if (!MenuConfigPath) {
		i = strlen(Locale);
		if (i>2) {
		    menu_file[strlen(menu_file)-(i-2)]=0;
		    MenuConfigPath = find_file(DEF_CONFIG_PATHS, menu_file);
		}
	    }
	}
	if (!MenuConfigPath)
	  MenuConfigPath = find_file(DEF_CONFIG_PATHS, DEF_MENUCONFIG);
    }
    if (MenuConfigPath) {
	int ok=1;
	
	if (stat(MenuConfigPath, &stats)<0) {
	    wSysError(_("could not stat menu configuration file \"%s\""), 
		      MenuConfigPath);
	    ok = 0;
	}
	if (!(S_IRUSR & stats.st_mode)) {
	    wWarning(_("no read permision to menu configuration file"));
	    ok = 0;
	}
	/* user changed menu config. Reread menu definition */
	if ((scr->root_menu==NULL || stats.st_mtime!=last_changed) && ok) {
#ifdef DEBUG
	    puts("Reparsing menu configs");
#endif	
	    last_changed = stats.st_mtime;

#ifdef USECPP
	    args = MakeCPPArgs(MenuConfigPath);
	    if (!args) {
		wWarning(_("could not make arguments for menu file "\
			"preprocessor"));
	    } else {
		sprintf(command, "%s %s %s", CPP_PATH, args, MenuConfigPath);
		wfree(args);
		file = popen(command, "r");
		if (!file) {
		    wSysError(_("could not open menu configuration"\
			" preprocessor"));
		} else {
		    cpp = 1;
		}
	    }
#endif /* USECPP */
	    if (!file) {
		file = fopen(MenuConfigPath, "r");
		if (file == NULL) {
		    wSysError(_("couldn't open menu configuration file \"%s\""),
			      MenuConfigPath);
		    return;
		}
	    }
	    scr->workspace_menu = NULL;
	    menu = parseMenu(scr, file);
#ifdef CPP
	    if (cpp) {
		pclose(file):
	    } else {
		fclose(file);
	    }
#else
	    fclose(file);
#endif
	    if (menu!=NULL) {
		if (scr->root_menu) {
		    wMenuDestroy(scr->root_menu, True);
		}
		scr->root_menu = menu;
	    } else {
		wMessageDialog(scr, _("Error"), 
			_("Could not read menu configuration\n(see console log for details)"),
			 WD_INFORMATION);
	    }
	}
    }
    menu = scr->root_menu;
    if (!menu) {
	menu = makeDefaultMenu(scr);
	scr->root_menu = menu;
    }
    if (menu) {
	wMenuMapAt(menu, x-menu->frame->width/2, y-menu->titlebar->height/2);
    }
}


static void
timeup(void *foo)
{
    *(int*)foo=1;
}

static char*
getselection(WScreen *scr)
{
    XEvent event;
    int timeover=0;
    WMagicNumber *id;
    
#ifdef DEBUG
    puts("getting selection");
#endif
    wRequestSelection(dpy, scr->no_focus_win, LastTimestamp);
    /* timeout on 1 sec. */
    id = wAddTimerHandler(1000, timeup, &timeover);
    while (!timeover) {
	wNextEvent(&event);
	if (event.type == SelectionNotify 
	    && event.xany.window==scr->no_focus_win) {
	    wDeleteTimerHandler(id);
#ifdef DEBUG
	    puts("selection ok");
#endif
	    return wGetSelection(dpy, scr->no_focus_win);
	} else {
	    DispatchEvent(&event);
	}
    }
    wWarning(_("selection timed-out"));
    return NULL;
}

#define S_NORMAL 0
#define S_ESCAPE 1
#define S_OPTION 2

/* 
 * state    	input   new-state	output
 * NORMAL	%	OPTION		<nil>
 * NORMAL	\	ESCAPE		<nil>
 * NORMAL	etc.	NORMAL		<input>
 * ESCAPE	any	NORMAL		<input>
 * OPTION	s	NORMAL		<selection buffer>
 * OPTION	w	NORMAL		<selected window id>
 * OPTION	a	NORMAL		<input text>
 * OPTION	etc.	NORMAL		%<input>
 */
static char*
expandOptions(WScreen *scr, char *cmdline)
{
    int ptr, optr, state, len, olen;
    char *out, *nout;
    char *selection=NULL;
    char tmpbuf[256];
    int slen;

    len = strlen(cmdline);
    olen = len+1;
    out = malloc(olen);
    if (!out) {
	wWarning(_("out of memory during expansion of \"%s\""));
	return NULL;
    }
    *out = 0;
    ptr = 0; 			       /* input line pointer */
    optr = 0;			       /* output line pointer */
    state = S_NORMAL;
    while (ptr < len) {
	switch (state) {
	 case S_NORMAL:
	    switch (cmdline[ptr]) {
	     case '\\':
		state = S_ESCAPE;
		break;
	     case '%':
		state = S_OPTION;
		break;
	     default:
		state = S_NORMAL;
		out[optr++]=cmdline[ptr];
		break;
	    }
	    break;
	 case S_ESCAPE:
	    out[optr++]=cmdline[ptr];
	    state = S_NORMAL;
	    break;
	 case S_OPTION:
	    state = S_NORMAL;
	    switch (cmdline[ptr]) {
	     case 'w':
		if (scr->focused_window
		    && scr->focused_window->flags.focused) {
		    sprintf(tmpbuf, "0x%x", 
			    (unsigned int)scr->focused_window->client_win);
		    slen = strlen(tmpbuf);
		    olen += slen;
		    nout = realloc(out,olen);
		    if (!nout) {
			wWarning(_("out of memory during expansion of \"%s\""));
			goto error;
		    }
		    out = nout;
		    strcat(out,tmpbuf);
		    optr+=slen;
		} else {
		    out[optr++]=' ';
		}
		break;
		
	     case 's': 		  
		if (!selection) {
		    if (!XGetSelectionOwner(dpy, XA_PRIMARY)) {
			wWarning(_("selection not available"));
			goto error;
		    }
		    selection = getselection(scr);
		}
		if (!selection) {
		    goto error;
		}
		slen = strlen(selection);
		olen += slen;
		nout = realloc(out,olen);
		if (!nout) {
		    wWarning(_("out of memory during expansion of \"%s\""));
		    goto error;
		}
		out = nout;
		strcat(out,selection);
		optr+=slen;
		break;
#ifdef USE_ARG_DIALOG
# define BUF_SIZE 256
	     case 'a':
		{
		    char *str = NULL;
		    char *msg = NULL;
		    if( cmdline[ptr+1] == '(' ) {
			/* parse the () bit to find out what usage message 
			 * the user wants to display */
			int i=0;
			msg = (char *)wmalloc(BUF_SIZE*sizeof(char));
			ptr+=2; /* Skip the '(' */
			while( cmdline[ptr] != ')' ) {
			    /* If usr's string is too long, ignore the rest
			     * of it... (this is reasonable, because there
			     * really is only room for a short string in
			     * the dialog window) */
			    if( i >= BUF_SIZE-1 )
			      ptr++;
			    else
			      msg[i++] = cmdline[ptr++];
			}
			msg[i] = '\0';
		    }
		    else
		      msg = _("Enter Command-Line Arguments");
		    
		    str = wInputDialog( scr, msg, "", NULL );
		    if( str != NULL ) {
			slen = strlen(str);
			olen += slen;
			nout = realloc( out, olen );
			if( !nout ) {
			    wWarning(_("out of memory during expansion of \"%a\""));
			    goto error;
			}
			out = nout;
			strcat( out, str );
			optr += slen;
		    }
		    else
		      /* The user must have hit "Cancel" */
		      goto error;
		}
		break;
#endif /* USE_ARG_DIALOG */
	     default:
		out[optr++]='%';
		out[optr++]=cmdline[ptr];
	    }
	    break;
	}
	out[optr]=0;
	ptr++;
    }
    if (selection)
      XFree(selection);
    return out;
    
    error:
    free(out);
    if (selection)
      XFree(selection);
    return NULL;
}
