/* rootmenu.c- user defined menu
 * 
 *  Window Maker window manager
 * 
 *  Copyright (c) 1997, 1998 Alfredo K. Kojima
 *  Copyright (c) 1998       Dan Pascu
 * 
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
 *  USA.
 */

#include "wconfig.h"

#ifndef LITE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <dirent.h>

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

#include "WindowMaker.h"
#include "actions.h"
#include "menu.h"
#include "funcs.h" 
#include "dialog.h"
#include "keybind.h"
#include "stacking.h"
#include "workspace.h"
#include "defaults.h"
#include "framewin.h"
#include "session.h"
#include "xmodifier.h"
#include <proplist.h>



extern char *Locale;

extern WDDomain *WDRootMenu;

extern Cursor wCursor[WCUR_LAST];

extern Time LastTimestamp;

extern WPreferences wPreferences;

extern int wScreenCount;

static WMenu *readMenuPipe(WScreen *scr, char **file_name);
static WMenu *readMenuFile(WScreen *scr, char *file_name);
static WMenu *readMenuDirectory(WScreen *scr, char *title, char **file_name,
				char *command);


typedef struct Shortcut {
    struct Shortcut *next;
    
    int modifier;
    KeyCode keycode;
    WMenuEntry *entry;
    WMenu *menu;
} Shortcut;



static Shortcut *shortcutList = NULL;


/*
 * 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
 * 
 * Commands may be preceded by SHORTCUT key
 * 
 * Built-in commands:
 *
 * INFO_PANEL - shows the Info Panel
 * LEGAL_PANEL - shows the Legal info panel
 * 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
 * SHEXEC <command> - execute a shell command
 * 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
 * OPEN_MENU file - read menu data from file which must be a valid menu file.
 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
 *              - read menu data from directory(ies) and
 * 		  eventually precede each with a command.
 * OPEN_MENU | command
 *              - opens command and uses its stdout to construct and insert
 *                the resulting menu in current position. The output of
 *                command must be a valid menu description.
 *                The space between '|' and command is optional.
 * SAVE_SESSION - saves the current state of the desktop, which include
 *		  all running applications, all their hints (geometry,
 *		  position on screen, workspace they live on, the dock
 *		  or clip from where they were launched, and
 *		  if minimized, shaded or hidden. Also saves the current
 *		  workspace the user is on. All will be restored on every
 *		  start of windowmaker until another SAVE_SESSION or
 *		  CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
 *		  WindowMaker domain file, then saving is automatically
 *		  done on every windowmaker exit, overwriting any
 *		  SAVE_SESSION or CLEAR_SESSION (see below). Also save
 *		  dock state now.
 * CLEAR_SESSION - clears any previous saved session. This will not have
 *		  any effect if SaveSessionOnExit is True.
 *							     
 */

#define MAX(a,b)	((a)>(b) ? (a) : (b))


#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) {
	ExecuteShellCommand(menu->frame->screen_ptr, cmdline);
	free(cmdline);
    }
    XUngrabPointer(dpy, CurrentTime);
    XSync(dpy, 0);
}


static void
exitCommand(WMenu *menu, WMenuEntry *entry)
{
    static int inside = 0;

    /* prevent reentrant calls */
    if (inside)
	return;
    inside = 1;

    if ((long)entry->clientdata==M_QUICK
	|| wMessageDialog(menu->frame->screen_ptr, _("Exit"),
			  _("Exit window manager?"), 
			  _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
#ifdef DEBUG
	printf("Exiting WindowMaker.\n");
#endif
	Shutdown(WSExitMode);
    }
    inside = 0;
}


static void
shutdownCommand(WMenu *menu, WMenuEntry *entry)
{
    static int inside = 0;
    int result;

    /* prevent reentrant calls */
    if (inside)
	return;
    inside = 1;

#define R_CANCEL 0
#define R_CLOSE 1
#define R_KILL 2

    
    result = R_CANCEL;
    if ((long)entry->clientdata==M_QUICK)
	result = R_CLOSE;
    else {
#ifdef XSMP_ENABLED
	if (wSessionIsManaged()) {
	    int r;
	    
	    r = wMessageDialog(menu->frame->screen_ptr, 
			       _("Close X session"), 
			       _("Close Window System session?\n"
				 "Kill might close applications with unsaved data."),
			       _("Close"), _("Kill"), _("Cancel"));
	    if (r==WAPRDefault)
		result = R_CLOSE;
	    else if (r==WAPRAlternate)
		result = R_KILL;
	} else
#endif
	{
	    int r;

	    r = wMessageDialog(menu->frame->screen_ptr, 
			       _("Kill X session"), 
			       _("Kill Window System session?\n"
				 "(all applications will be closed)"),
			       _("Kill"), _("Cancel"), NULL);
	    if (r==WAPRDefault)
		result = R_KILL;
	}
    } 
    
    if (result!=R_CANCEL) {
#ifdef XSMP_ENABLED
	if (result == R_CLOSE) {
	    Shutdown(WSLogoutMode);
	} else 
#endif /* XSMP_ENABLED */
	{
	    Shutdown(WSKillMode);
	}
    }
#undef R_CLOSE
#undef R_CANCEL
#undef R_KILL
    inside = 0;
}


static void
restartCommand(WMenu *menu, WMenuEntry *entry)
{
    Shutdown(WSRestartPreparationMode);
    Restart((char*)entry->clientdata, False);
    Restart(NULL, True);
}


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


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

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
saveSessionCommand(WMenu *menu, WMenuEntry *entry)
{
    if (!wPreferences.save_session_on_exit)
	wSessionSaveState(menu->frame->screen_ptr);

    wScreenSaveState(menu->frame->screen_ptr);
}


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


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


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


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


static char*
getLocalizedMenuFile(char *menu)
{
    char *buffer;
    char *ptr;

    if (!Locale)
	return NULL;

    buffer = wmalloc(strlen(menu)+32);

    /* try menu.locale_name */
    sprintf(buffer, "%s.%s", menu, Locale);
    if (access(buffer, F_OK)==0) {
	return buffer;
    }
    /* check if it is in the form aa_bb.encoding and check for aa_bb */
    ptr = strchr(Locale, '.');
    if (ptr) {
	*ptr = 0;
	if (access(buffer, F_OK)==0) {
	    return buffer;
	}
    }
    /* now check for aa */
    ptr = strchr(buffer, '_');
    if (ptr) {
	*ptr = 0;
	if (access(buffer, F_OK)==0) {
	    return buffer;
	}
    }

    return NULL;
}


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



Bool
wRootMenuPerformShortcut(XEvent *event)
{
    Shortcut *ptr;
    int modifiers;
    int done = 0;
    
    /* ignore CapsLock */
    modifiers = event->xkey.state & ValidModMask;
    
    for (ptr = shortcutList; ptr!=NULL; ptr = ptr->next) {
	if (ptr->keycode==0) 
	    continue;
	
	if (ptr->keycode==event->xkey.keycode && (ptr->modifier==modifiers)) {
	    (*ptr->entry->callback)(ptr->menu, ptr->entry);
	    done = True;
	}
    }
    return done;
}


void
wRootMenuBindShortcuts(Window window)
{
    Shortcut *ptr;
    
    ptr = shortcutList;
    while (ptr) {
	if (ptr->modifier!=AnyModifier) {
	    XGrabKey(dpy, ptr->keycode, ptr->modifier|LockMask,
		     window, True, GrabModeAsync, GrabModeAsync);
#ifdef NUMLOCK_HACK
	    wHackedGrabKey(ptr->keycode, ptr->modifier,
			   window, True, GrabModeAsync, GrabModeAsync);	    
#endif
	}
	XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True, 
		 GrabModeAsync, GrabModeAsync);
	ptr = ptr->next;
    }
}


static void
rebindKeygrabs(WScreen *scr)
{
    WWindow *wwin;
    
    wwin = scr->focused_window;
    
    while (wwin!=NULL) {
	XUngrabKey(dpy, AnyKey, AnyModifier, wwin->frame->core->window);

	if (!WFLAGP(wwin, no_bind_keys)) {
	    wWindowSetKeyGrabs(wwin);
	}
	wwin = wwin->prev;
    }
}


static void
removeShortcutsForMenu(WMenu *menu)
{
    Shortcut *ptr, *tmp;
    Shortcut *newList = NULL;

    ptr = shortcutList;
    while (ptr!=NULL) {
	tmp = ptr->next;
	if (ptr->menu == menu) {
	    free(ptr);
	} else {
	    ptr->next = newList;
	    newList = ptr;
	}
	ptr = tmp;
    }
    shortcutList = newList;
    menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
}


static Bool
addShortcut(char *file, char *shortcutDefinition, WMenu *menu,
	    WMenuEntry *entry)
{
    Shortcut *ptr;    
    KeySym ksym;
    char *k;
    char buf[128], *b;

    ptr = wmalloc(sizeof(Shortcut));
    
    strcpy(buf, shortcutDefinition);
    b = (char*)buf;

    /* get modifiers */
    ptr->modifier = 0;
    while ((k = strchr(b, '+'))!=NULL) {
	int mod;
	
	*k = 0;
	mod = wXModifierFromKey(b);
	if (mod<0) {
	    wwarning(_("%s:invalid key modifier \"%s\""), file, b);
	    free(ptr);
	    return False;
	}
	ptr->modifier |= mod;
	
	b = k+1;
    }
    
    /* get key */
    ksym = XStringToKeysym(b);
    
    if (ksym==NoSymbol) {
	wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
		 file, shortcutDefinition, entry->text);
	free(ptr);
	return False;
    }
    
    ptr->keycode = XKeysymToKeycode(dpy, ksym);
    if (ptr->keycode==0) {
	wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
		 shortcutDefinition, entry->text);
	free(ptr);
	return False;
    }

    ptr->menu = menu;
    ptr->entry = entry;
    
    ptr->next = shortcutList;
    shortcutList = ptr;
    
    menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
    
    return True;
}


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

static char*
cropline(char *line)
{
    char *end;
    
    if (strlen(line)==0)
	return line;
    
    end = &(line[strlen(line)])-1;
    while (isspace(*line) && *line!=0) line++;
    while (end>line && isspace(*end)) {
	*end=0;
	end--;
    }
    return line;
}


static  char*
next_token(char *line, char **next)
{
    char *tmp, c;
    char *ret;

    *next = NULL;
    while (*line==' ' || *line=='\t') line++;

    tmp = line;

    if (*tmp=='"') {
        tmp++; line++;
        while (*tmp!=0 && *tmp!='"') tmp++;
        if (*tmp!='"') {
            wwarning(_("%s: unmatched '\"' in menu file"), line);
            return NULL;
        }
    } else {
        do {
            if (*tmp=='\\')
                tmp++;

            if (*tmp!=0)
                tmp++;

        } while (*tmp!=0 && *tmp!=' ' && *tmp!='\t');
    }

    c = *tmp;
    *tmp = 0;
    ret = wstrdup(line);
    *tmp = c;

    if (c==0)
        return ret;
    else
        tmp++;

    /* skip blanks */
    while (*tmp==' ' || *tmp=='\t') tmp++;

    if (*tmp!=0)
        *next = tmp;

    return ret;
}


static void
separateCommand(char *line, char ***file, char **command)
{
    char *token, *tmp = line;
    WMBag *bag = WMCreateBag(4);
    int count, i;

    *file = NULL;
    *command = NULL;
    do {
        token = next_token(tmp, &tmp);
        if (token) {
            if (strcmp(token, "WITH")==0) {
                if (tmp!=NULL && *tmp!=0)
                    *command = wstrdup(tmp);
                else
                    wwarning(_("%s: missing command"), line);
                break;
            }
	    WMPutInBag(bag, token);
        }
    } while (token!=NULL && tmp!=NULL);

    count = WMGetBagItemCount(bag);
    if (count>0) {
        *file = wmalloc(sizeof(char*)*(count+1));
        (*file)[count] = NULL;
        for (i = 0; i < count; i++) {
            (*file)[i] = WMGetFromBag(bag, i);
        }
    }
    WMFreeBag(bag);
}


static void
constructMenu(WMenu *menu, WMenuEntry *entry)
{
    WMenu *submenu;
    struct stat stat_buf;
    char **path;
    char *cmd;
    char *lpath = NULL;
    int i, first=-1;
    time_t last=0;
    
    separateCommand((char*)entry->clientdata, &path, &cmd);
    if (path == NULL || *path==NULL || **path==0) {
	wwarning(_("invalid OPEN_MENU specification: %s"),
		 (char*)entry->clientdata);
	return;
    }
    
    if (path[0][0]=='|') {
        /* pipe menu */
	
        if (!menu->cascades[entry->cascade] || 
            menu->cascades[entry->cascade]->timestamp == 0) { 
            /* parse pipe */

	    submenu = readMenuPipe(menu->frame->screen_ptr, path);

            /* there's no automatic reloading */
            if(submenu != NULL)
                submenu->timestamp = 1;
        } else { 
            submenu = NULL; 
        }
  
    } else {
        i=0;
        while(path[i] != NULL) {
	    char *tmp;

	    if (strcmp(path[i], "-noext")==0) {
		i++;
		continue;
	    }
	    
	    tmp = wexpandpath(path[i]);
	    free(path[i]);
	    lpath = getLocalizedMenuFile(tmp);
	    if (lpath) {
		free(tmp);
		path[i] = lpath;
		lpath = NULL;
	    } else {
		path[i] = tmp;
	    }

            if (stat(path[i], &stat_buf)==0) {
                if (last < stat_buf.st_mtime)
                    last = stat_buf.st_mtime;
                if (first<0)
                    first=i;
            } else {
                wsyserror(_("%s:could not stat menu"), path[i]);
                /*goto finish;*/
            }

            i++;
        }

        if (first < 0) {
            wsyserror(_("%s:could not stat menu:%s"), "OPEN_MENU",
                      (char*)entry->clientdata);
            goto finish;
        }
        stat(path[first], &stat_buf);
	if (!menu->cascades[entry->cascade]
	    || menu->cascades[entry->cascade]->timestamp < last) {

	    if (S_ISDIR(stat_buf.st_mode)) {
		/* menu directory */
		submenu = readMenuDirectory(menu->frame->screen_ptr,
                                            entry->text, path, cmd);
		if (submenu)
		    submenu->timestamp = last;
	    } else if (S_ISREG(stat_buf.st_mode)) {
		/* menu file */

		if (cmd || path[1])
		    wwarning(_("too many parameters in OPEN_MENU: %s"), 
			     (char*)entry->clientdata);

		submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
		if (submenu)
		    submenu->timestamp = stat_buf.st_mtime;
	    } else {
		submenu = NULL;
	    }
	} else {
	    submenu = NULL;
	}
    }

    if (submenu) {
	wMenuEntryRemoveCascade(menu, entry);
	wMenuEntrySetCascade(menu, entry, submenu);
    }

finish:
    i = 0;
    while (path[i]!=NULL)
        free(path[i++]);
    free(path);
    if (cmd)
	free(cmd);
}


static WMenuEntry*
addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
{
    WMenu *wsmenu;
    WMenuEntry *entry;

    if (scr->flags.added_workspace_menu) {
	wwarning(_("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
	return NULL;
    } else {
	scr->flags.added_workspace_menu = 1;
	
	wsmenu = wWorkspaceMenuMake(scr, True);
	scr->workspace_menu = wsmenu;
	entry = wMenuAddCallback(menu, title, NULL, NULL);
	wMenuEntrySetCascade(menu, entry, wsmenu);
	
	wWorkspaceMenuUpdate(scr, wsmenu);
    }
    return entry;
}


static WMenuEntry*
addMenuEntry(WMenu *menu, char *title, char *shortcut, char *command,
	     char *params, char *file_name)
{
    WScreen *scr;
    WMenuEntry *entry = NULL;
    Bool shortcutOk = False;

    if (!menu)
	return NULL;
    scr = menu->frame->screen_ptr;
    if (strcmp(command, "OPEN_MENU")==0) {
	if (!params) {
	    wwarning(_("%s:missing parameter for menu command \"%s\""),
		     file_name, command);
	} else {
	    WMenu *dummy;
	    char *path;
	    
            path = wfindfile(DEF_CONFIG_PATHS, params);
	    if (!path) {
		path = wstrdup(params);
	    }
	    dummy = wMenuCreate(scr, title, False);
	    dummy->on_destroy = removeShortcutsForMenu;
	    entry = wMenuAddCallback(menu, title, constructMenu, path);
	    entry->free_cdata = free;
	    wMenuEntrySetCascade(menu, entry, dummy);
	}
    } else if (strcmp(command, "EXEC")==0) {
	if (!params)
	    wwarning(_("%s:missing parameter for menu command \"%s\""),
		     file_name, command);
	else {
	    entry = wMenuAddCallback(menu, title, execCommand, 
				     wstrappend("exec ", params));
	    entry->free_cdata = free;
	    shortcutOk = True;
	}
    } else if (strcmp(command, "SHEXEC")==0) {
	if (!params)
	    wwarning(_("%s:missing parameter for menu command \"%s\""),
		     file_name, command);
	else {
	    entry = wMenuAddCallback(menu, title, execCommand,
				     wstrdup(params));
	    entry->free_cdata = free;
	    shortcutOk = True;
	}
    } else if (strcmp(command, "EXIT")==0) {
	
	if (params && strcmp(params, "QUICK")==0)
	    entry = wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
	else
	    entry = wMenuAddCallback(menu, title, exitCommand, NULL);

	shortcutOk = True;
    } else if (strcmp(command, "SHUTDOWN")==0) {
	    
	if (params && strcmp(params, "QUICK")==0)
	    entry = wMenuAddCallback(menu, title, shutdownCommand, 
				     (void*)M_QUICK);
	else
	    entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
	
	shortcutOk = True;
    } else if (strcmp(command, "REFRESH")==0) {
	entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
	
	shortcutOk = True;
    } else if (strcmp(command, "WORKSPACE_MENU")==0) {
	entry = addWorkspaceMenu(scr, menu, title);
	
	shortcutOk = True;
    } else if (strcmp(command, "ARRANGE_ICONS")==0) {
	entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
	
	shortcutOk = True;
    } else if (strcmp(command, "HIDE_OTHERS")==0) {
	entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
	
	shortcutOk = True;
    } else if (strcmp(command, "SHOW_ALL")==0) {
	entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
	
	shortcutOk = True;
    } else if (strcmp(command, "RESTART")==0) {
	entry = wMenuAddCallback(menu, title, restartCommand, 
				 params ? wstrdup(params) : NULL);
	entry->free_cdata = free;
	shortcutOk = True;
    } else if (strcmp(command, "SAVE_SESSION")==0) {
        entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
        
	shortcutOk = True;
    } else if (strcmp(command, "CLEAR_SESSION")==0) {
        entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
	shortcutOk = True;
    } else if (strcmp(command, "INFO_PANEL")==0) {
	entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
	shortcutOk = True;
    } else if (strcmp(command, "LEGAL_PANEL")==0) {
	entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
	shortcutOk = True;
    } else {
	wwarning(_("%s:unknown command \"%s\" in menu config."), file_name,
		 command);
	
	return NULL;
    }
    
    if (shortcut && entry) {
	if (!shortcutOk) {
	    wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name,
		     title);
	} else {
	    if (addShortcut(file_name, shortcut, menu, entry)) {
	      
		entry->rtext = GetShortcutString(shortcut);
		 /*
		entry->rtext = wstrdup(shortcut);
		  */
	    }
	}
    }

    return entry;
}



/*******************   Menu Configuration From File   *******************/

static void
separateline(char *line, char *title, char *command, char *parameter,
	     char *shortcut)
{
    int l, i;
    
    l = strlen(line);

    *title = 0;
    *command = 0;
    *parameter = 0;
    *shortcut = 0;
    /* get the title */
    while (isspace(*line) && (*line!=0)) line++;
    if (*line=='"') {
	line++;
	i=0;
	while (line[i]!='"' && (line[i]!=0)) i++;
	if (line[i]!='"')
	  return;
    } else {
	i=0;
	while (!isspace(line[i]) && (line[i]!=0)) i++;
    }
    strncpy(title, line, i);
    title[i++]=0;
    line+=i;

    /* get the command or shortcut keyword */
    while (isspace(*line) && (*line!=0)) line++;
    if (*line==0)
      return;
    i=0;
    while (!isspace(line[i]) && (line[i]!=0)) i++;
    strncpy(command, line, i);
    command[i++]=0;
    line+=i;

    if (strcmp(command, "SHORTCUT")==0) {
	/* get the shortcut key */
	while (isspace(*line) && (*line!=0)) line++;
	if (*line=='"') {
	    line++;
	    i=0;
	    while (line[i]!='"' && (line[i]!=0)) i++;
	    if (line[i]!='"')
		return;
	} else {
	    i=0;
	    while (!isspace(line[i]) && (line[i]!=0)) i++;
	}
	strncpy(shortcut, line, i);
	shortcut[i++]=0;
	line+=i;
	
	*command=0;
	
	/* get the command */
	while (isspace(*line) && (*line!=0)) line++;
	if (*line==0)
	    return;
	i=0;
	while (!isspace(line[i]) && (line[i]!=0)) i++;
	strncpy(command, line, i);
	command[i++]=0;
	line+=i;
    }

    /* get the parameters */
    while (isspace(*line) && (*line!=0)) line++;
    if (*line==0)
      return;
    
    if (*line=='"') {
	line++;
	l = 0;
	while (line[l]!=0 && line[l]!='"') {
	    parameter[l] = line[l];
	    l++;
	}
	parameter[l] = 0;
	return;
    }
    
    l = strlen(line);
    while (isspace(line[l]) && (l>0)) l--;
    strncpy(parameter, line, l);
    parameter[l]=0;
}


static WMenu*
parseCascade(WScreen *scr, WMenu *menu, FILE *file, char *file_name)
{
    char linebuf[MAXLINE];
    char elinebuf[MAXLINE];
    char title[MAXLINE];
    char command[MAXLINE];
    char shortcut[MAXLINE];
    char params[MAXLINE];
    char *line;
    
    while (!IsEof(file)) {
	int lsize, ok;
	
	ok = 0;
	fgets(linebuf, MAXLINE, file);
	line = cropline(linebuf);
	lsize = strlen(line);
	do {
	    if (line[lsize-1]=='\\') {
		char *line2;
		int lsize2;
		fgets(elinebuf, MAXLINE, file);
		line2=cropline(elinebuf);
		lsize2=strlen(line2);
		if (lsize2+lsize>MAXLINE) {
		    wwarning(_("%s:maximal line size exceeded in menu config: %s"),
			     file_name, line);
		    ok=2;
		} else {
		    line[lsize-1]=0;
		    lsize+=lsize2-1;
		    strcat(line, line2);
		}
	    } else {
		ok=1;
	    }
	} while (!ok && !IsEof(file));
	if (ok==2)
	  continue;
	
	if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/')) 
	  continue;

	
	separateline(line, title, command, params, shortcut);
	
	if (!command[0]) {
	    wwarning(_("%s:missing command in menu config: %s"), file_name,
		     line);
	    goto error;
	}

	if (strcasecmp(command, "MENU")==0) {
	    WMenu *cascade;
	    
	    /* start submenu */

	    cascade = wMenuCreate(scr, title, False);
	    cascade->on_destroy = removeShortcutsForMenu;
	    if (parseCascade(scr, cascade, file, file_name)==NULL) {
		wMenuDestroy(cascade, True);
	    } else {
		wMenuEntrySetCascade(menu, 
				     wMenuAddCallback(menu, title, NULL, NULL),
				     cascade);
	    }
	} else if (strcasecmp(command, "END")==0) {
	    /* end of menu */
	    return menu;
	    
	} else {
	    /* normal items */
	    addMenuEntry(menu, title, shortcut[0] ? shortcut : NULL, command, 
			 params[0] ? params : NULL, file_name);
	}
    }

    wwarning(_("%s:syntax error in menu file:END declaration missing"),
	     file_name);
    return menu;

    error:
    return menu;
}


static WMenu*
readMenuFile(WScreen *scr, char *file_name)
{
    WMenu *menu=NULL;
    FILE *file = NULL;
    char linebuf[MAXLINE];
    char title[MAXLINE];
    char shortcut[MAXLINE];
    char command[MAXLINE];
    char params[MAXLINE];
    char *line;
#ifdef USECPP
    char *args;
    int cpp = 0;
#endif

#ifdef USECPP
    if (!wPreferences.flags.nocpp) {
	args = MakeCPPArgs(file_name);
	if (!args) {
	    wwarning(_("could not make arguments for menu file preprocessor"));
	} else {
	    sprintf(command, "%s %s %s", CPP_PATH, args, file_name);
	    free(args);
	    file = popen(command, "r");
	    if (!file) {
		wsyserror(_("%s:could not open/preprocess menu file"), 
			  file_name);
	    } else {
		cpp = 1;
	    }
	}
    }
#endif /* USECPP */

    if (!file) {
	file = fopen(file_name, "r");
	if (!file) {
	    wsyserror(_("%s:could not open menu file"), file_name);
	    return NULL;
	}
    }
    
    while (!IsEof(file)) {
	if (!fgets(linebuf, MAXLINE, file))
	    break;
	line = cropline(linebuf);
	if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/')) 
	  continue;

	separateline(line, title, command, params, shortcut);
	
	if (!command[0]) {
	    wwarning(_("%s:missing command in menu config: %s"), file_name,
		     line);
	    break;
	}
	if (strcasecmp(command, "MENU")==0) {
	    menu = wMenuCreate(scr, title, True);
	    menu->on_destroy = removeShortcutsForMenu;
	    if (!parseCascade(scr, menu, file, file_name)) {
		wMenuDestroy(menu, True);
	    }
	    break;
	} else {
	    wwarning(_("%s:invalid menu file. MENU command is missing"), 
		     file_name);
	    break;
	}
    }
    
#ifdef CPP
    if (cpp) {
	if (pclose(file)==-1) {
	    wsyserror(_("error reading preprocessed menu data"));
	}
    } else {
	fclose(file);
    }
#else
    fclose(file);
#endif
    
    return menu;
}

/************    Menu Configuration From Pipe      *************/

static WMenu*
readMenuPipe(WScreen *scr, char **file_name) 
{
    WMenu *menu=NULL;
    FILE *file = NULL;
    char linebuf[MAXLINE];
    char title[MAXLINE];
    char command[MAXLINE];
    char params[MAXLINE];
    char shortcut[MAXLINE];
    char *line;
    char * filename;
    char flat_file[MAXLINE];
    int i;
#ifdef USECPP
    char *args;
    int cpp = 0;
#endif

    flat_file[0] = '\0';

    for(i = 0 ; file_name[i] != NULL ; i++) {
	strcat(flat_file, file_name[i]);
	strcat(flat_file, " ");
    }
    filename = flat_file+1;

#ifdef USECPP
    if (!wPreferences.flags.nocpp) {
       args = MakeCPPArgs(filename);
       if (!args) {
           wwarning(_("could not make arguments for menu file preprocessor"));
       } else {
           sprintf(command, "%s | %s %s", filename, CPP_PATH, args);

           free(args);
           file = popen(command, "r");
           if (!file) {
               wsyserror(_("%s:could not open/preprocess menu file"), filename);
           } else {
               cpp = 1;
           }
       }
    }

#endif /* USECPP */

    if (!file) {
       file = popen(filename, "r");

       if (!file) {
           wsyserror(_("%s:could not open menu file"), filename);
           return NULL;
       }
    }

    while (!IsEof(file)) {
	if (!fgets(linebuf, MAXLINE, file))
	    break;
	line = cropline(linebuf);
	if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/')) 
	    continue;

	separateline(line, title, command, params, shortcut);

	if (!command[0]) {
	    wwarning(_("%s:missing command in menu config: %s"), file_name,
		     line);
	    break;
	}
	if (strcasecmp(command, "MENU")==0) {
	    menu = wMenuCreate(scr, title, True);
	    menu->on_destroy = removeShortcutsForMenu;
	    if (!parseCascade(scr, menu, file, filename)) {
		wMenuDestroy(menu, True);
	    }
	    break;
	} else {
	    wwarning(_("%s:no title given for the root menu"), filename);
	    break;
	}
    }

    pclose(file);

    return menu;
}



typedef struct {
    char *name;
    int index;
} dir_data;


static int
myCompare(const void *d1, const void *d2)
{
    dir_data *p1 = *(dir_data**)d1;
    dir_data *p2 = *(dir_data**)d2;

    return strcmp(p1->name, p2->name);
}


/************  Menu Configuration From Directory   *************/

static Bool
isFilePackage(char *file)
{
    int l;

    /* check if the extension indicates this file is a 
     * file package. For now, only recognize .themed */

    l = strlen(file);

    if (l > 7 && strcmp(&(file[l-7]), ".themed")==0) {
	return True;
    } else {
	return False;
    }
}


static WMenu*
readMenuDirectory(WScreen *scr, char *title, char **path, char *command)
{
    DIR *dir;
    struct dirent *dentry;
    struct stat stat_buf;
    WMenu *menu=NULL;
    char *buffer;
    WMBag *dirs = NULL, *files = NULL;
    int length, i, have_space=0;
    dir_data *data;
    int stripExtension = 0;

    
    dirs = WMCreateBag(16);
    files = WMCreateBag(16);
    
    i=0;
    while (path[i]!=NULL) {
	if (strcmp(path[i], "-noext")==0) {
	    stripExtension = 1;
	    i++;
	    continue;
	}

        dir = opendir(path[i]);
        if (!dir) {
            i++;
            continue;
        }

        while ((dentry = readdir(dir))) {

            if (strcmp(dentry->d_name, ".")==0 ||
                strcmp(dentry->d_name, "..")==0)
                continue;

	    if (dentry->d_name[0] == '.')
		continue;

            buffer = malloc(strlen(path[i])+strlen(dentry->d_name)+4);
            if (!buffer) {
                wsyserror(_("out of memory while constructing directory menu %s"),
                         path[i]);
                break;
            }

            strcpy(buffer, path[i]);
            strcat(buffer, "/");
            strcat(buffer, dentry->d_name);

            if (stat(buffer, &stat_buf)!=0) {
                wsyserror(_("%s:could not stat file \"%s\" in menu directory"),
                          path[i], dentry->d_name);
            } else {
		Bool isFilePack = False;

		data = NULL;
                if (S_ISDIR(stat_buf.st_mode)
		    && !(isFilePack = isFilePackage(dentry->d_name))) {

                    /* access always returns success for user root */
                    if (access(buffer, X_OK)==0) {
                        /* Directory is accesible. Add to directory list */

			data = (dir_data*) wmalloc(sizeof(dir_data));
			data->name = wstrdup(dentry->d_name);
			data->index = i;

			WMPutInBag(dirs, data);
                    }
                } else if (S_ISREG(stat_buf.st_mode) || isFilePack) {
                    /* Hack because access always returns X_OK success for user root */
#define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
                    if ((command!=NULL && access(buffer, R_OK)==0) ||
                        (command==NULL && access(buffer, X_OK)==0 &&
                         (stat_buf.st_mode & S_IXANY))) {

			data = (dir_data*) wmalloc(sizeof(dir_data));
			data->name = wstrdup(dentry->d_name);
			data->index = i;

			WMPutInBag(files, data);
                    }
                }
            }
            free(buffer);
        }

        closedir(dir);
        i++;
    }
    
    if (!WMGetBagItemCount(dirs) && !WMGetBagItemCount(files)) {
	WMFreeBag(dirs);
	WMFreeBag(files);
        return NULL;
    }

    WMSortBag(dirs, myCompare);
    WMSortBag(files, myCompare);

    menu = wMenuCreate(scr, title, False);
    menu->on_destroy = removeShortcutsForMenu;

    for (i = 0; i < WMGetBagItemCount(dirs); i++) {
        /* New directory. Use same OPEN_MENU command that was used
         * for the current directory. */
        dir_data *d = (dir_data*)WMGetFromBag(dirs, i);

        length = strlen(path[d->index])+strlen(d->name)+6;
	if (stripExtension)
	    length += 7;
        if (command)
            length += strlen(command) + 6;
        buffer = malloc(length);
        if (!buffer) {
            wsyserror(_("out of memory while constructing directory menu %s"),
                     path[d->index]);
            break;
        }
	
	buffer[0] = '\0';
        if (stripExtension)
            strcat(buffer, "-noext ");

        have_space = strchr(path[d->index], ' ')!=NULL ||
                     strchr(d->name, ' ')!=NULL;

	if (have_space)
            strcat(buffer, "\"");
	strcat(buffer, path[d->index]);

        strcat(buffer, "/");
        strcat(buffer, d->name);
        if (have_space)
            strcat(buffer, "\"");
        if (command) {
            strcat(buffer, " WITH ");
            strcat(buffer, command);
        }

        addMenuEntry(menu, d->name, NULL, "OPEN_MENU", buffer, path[d->index]);

        free(buffer);
	if (d->name)
	    free(d->name);
	free(d);
    }

    for (i = 0; i < WMGetBagItemCount(files); i++) {
        /* executable: add as entry */
        dir_data *f = (dir_data*)WMGetFromBag(files, i);

        length = strlen(path[f->index])+strlen(f->name)+6;
        if (command)
            length += strlen(command);

        buffer = malloc(length);
        if (!buffer) {
            wsyserror(_("out of memory while constructing directory menu %s"),
                     path[f->index]);
            break;
        }

        have_space = strchr(path[f->index], ' ')!=NULL ||
                     strchr(f->name, ' ')!=NULL;
        if (command!=NULL) {
            strcpy(buffer, command);
            strcat(buffer, " ");
            if (have_space)
                strcat(buffer, "\"");
            strcat(buffer, path[f->index]);
        } else {
            if (have_space) {
                buffer[0] = '"';
                buffer[1] = 0;
                strcat(buffer, path[f->index]);
            } else {
                strcpy(buffer, path[f->index]);
            }
        }
        strcat(buffer, "/");
        strcat(buffer, f->name);
        if (have_space)
            strcat(buffer, "\"");

	if (stripExtension) {
	    char *ptr = strrchr(f->name, '.');
	    if (ptr && ptr!=f->name)
		*ptr = 0;
	}
        addMenuEntry(menu, f->name, NULL, "SHEXEC", buffer, path[f->index]);

        free(buffer);
	if (f->name)
	    free(f->name);
	free(f);
    }
    
    WMFreeBag(files);
    WMFreeBag(dirs);

    return menu;
}


/************  Menu Configuration From WMRootMenu   *************/

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





/*
 *---------------------------------------------------------------------- 
 * configureMenu--
 * 	Reads root menu configuration from defaults database.
 * 
 *---------------------------------------------------------------------- 
 */
static WMenu*
configureMenu(WScreen *scr, proplist_t definition)
{
    WMenu *menu = NULL;
    proplist_t elem;
    int i, count;
    proplist_t title, command, params;
    char *tmp, *mtitle;

    
    if (PLIsString(definition)) {
	struct stat stat_buf;
	char *path = NULL;
	Bool menu_is_default = False;

	/* menu definition is a string. Probably a path, so parse the file */

	tmp = wexpandpath(PLGetString(definition));

	path = getLocalizedMenuFile(tmp);

	if (!path)
	    path = wfindfile(DEF_CONFIG_PATHS, tmp);

	if (!path) {
	    path = wfindfile(DEF_CONFIG_PATHS, DEF_MENU_FILE);
	    menu_is_default = True;
	}

	if (!path) {
	    wsyserror(_("could not find menu file \"%s\" referenced in WMRootMenu"),
		     tmp);
	    free(tmp);
	    return NULL;
	}

	if (stat(path, &stat_buf)<0) {
	    wsyserror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
	    free(path);
	    free(tmp);
	    return NULL;
	}

	if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
	    /* if the pointer in WMRootMenu has changed */
	    || WDRootMenu->timestamp > scr->root_menu->timestamp) {
	    
	    if (menu_is_default) {
		wwarning(_("using default menu file \"%s\" as the menu referenced in WMRootMenu could not be found "),
			 path);
	    }

	    menu = readMenuFile(scr, path);
	    if (menu)
		menu->timestamp = MAX(stat_buf.st_mtime, WDRootMenu->timestamp);
	} else {
	    menu = NULL;
	}
	free(path);
	free(tmp);

	return menu;
    }

    count = PLGetNumberOfElements(definition);
    if (count==0)
	return NULL;

    elem = PLGetArrayElement(definition, 0);
    if (!PLIsString(elem)) {
	tmp = PLGetDescription(elem);
	wwarning(_("%s:format error in root menu configuration \"%s\""), 
		 "WMRootMenu", tmp);
	free(tmp);
	return NULL;
    }
    mtitle = PLGetString(elem);
    
    menu = wMenuCreate(scr, mtitle, False);
    menu->on_destroy = removeShortcutsForMenu;

#ifdef GLOBAL_SUBMENU_FILE
    {
	WMenu *submenu;
	WMenuEntry *mentry;

	submenu = readMenuFile(scr, GLOBAL_SUBMENU_FILE);

	if (submenu) {
	    mentry = wMenuAddCallback(menu, submenu->frame->title, NULL, NULL);
	    wMenuEntrySetCascade(menu, mentry, submenu);
	}
    }
#endif

    for (i=1; i<count; i++) {
	elem = PLGetArrayElement(definition, i);
#if 0
	if (PLIsString(elem)) {
	    char *file;
	    
	    file = PLGetString(elem);
	    
	}
#endif	
	if (!PLIsArray(elem) || PLGetNumberOfElements(elem) < 2)
	    goto error;
	
	if (PLIsArray(PLGetArrayElement(elem,1))) {
	    WMenu *submenu;
	    WMenuEntry *mentry;
	    
	    /* submenu */
	    submenu = configureMenu(scr, elem);
	    if (submenu) {
		mentry = wMenuAddCallback(menu, submenu->frame->title, NULL, 
					  NULL);
		wMenuEntrySetCascade(menu, mentry, submenu);
	    }
	} else {
	    int idx = 0;
	    char *shortcut;
	    /* normal entry */

	    title = PLGetArrayElement(elem, idx++);
	    shortcut = PLGetArrayElement(elem, idx++);
	    if (strcmp(PLGetString(shortcut), "SHORTCUT")==0) {
		shortcut = PLGetArrayElement(elem, idx++);
		command = PLGetArrayElement(elem, idx++);
	    } else {
		command = shortcut;
		shortcut = NULL;
	    }
	    params = PLGetArrayElement(elem, idx++);

	    if (!title || !command)
		goto error;
	    
	    addMenuEntry(menu, PLGetString(title), 
			 shortcut ? PLGetString(shortcut) : NULL, 
			 PLGetString(command),
			 params ? PLGetString(params) : NULL, "WMRootMenu");
	}
	continue;

      error:
	tmp = PLGetDescription(elem);
	wwarning(_("%s:format error in root menu configuration \"%s\""), 
		 "WMRootMenu", tmp);
	free(tmp);
    }
    
    return menu;
}








/*
 *---------------------------------------------------------------------- 
 * OpenRootMenu--
 * 	Opens the root menu, parsing the menu configuration from the
 * defaults database.
 *	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:
 * Construction of OPEN_MENU entries are delayed to the moment the
 * user map's them.
 *---------------------------------------------------------------------- 
 */
void
OpenRootMenu(WScreen *scr, int x, int y, int keyboard)
{
    WMenu *menu=NULL;
    proplist_t definition;
    /*
    static proplist_t domain=NULL;
    
    if (!domain) {
	domain = PLMakeString("WMRootMenu");
    }
     */
    
    scr->flags.root_menu_changed_shortcuts = 0;
    scr->flags.added_workspace_menu = 0;

    if (scr->root_menu && scr->root_menu->flags.mapped) {
	menu = scr->root_menu;
	if (!menu->flags.buttoned) {
	    wMenuUnmap(menu);
	} else {
	    wRaiseFrame(menu->frame->core);
	    
	    if (keyboard)
		wMenuMapAt(menu, 0, 0, True);
	    else
		wMenuMapCopyAt(menu, x-menu->frame->core->width/2, y);
	}
	return;
    }
    
    
    definition = WDRootMenu->dictionary;

/*
    definition = PLGetDomain(domain);
 */
    if (definition) {
	if (PLIsArray(definition)) {
	    if (!scr->root_menu 
		|| WDRootMenu->timestamp > scr->root_menu->timestamp) {
		menu = configureMenu(scr, definition);
		if (menu)
		    menu->timestamp = WDRootMenu->timestamp;

	    } else
		menu = NULL;
	} else {
	    menu = configureMenu(scr, definition);
	}
    }

    if (!menu) {
	/* menu hasn't changed or could not be read */
	if (!scr->root_menu) {
	    wMessageDialog(scr, _("Error"),
			  _("The applications menu could not be loaded. "
			    "Look at the console output for a detailed "
			    "description of the errors."),
			   _("OK"), NULL, NULL);

	    menu = makeDefaultMenu(scr);
	    scr->root_menu = menu;
	}
	menu = scr->root_menu;
    } else {
	/* new root menu */
	if (scr->root_menu)
	    wMenuDestroy(scr->root_menu, True);
	scr->root_menu = menu;
    }
    if (menu) {
	wMenuMapAt(menu, x-menu->frame->core->width/2, y, keyboard);
    }
    
    if (scr->flags.root_menu_changed_shortcuts)
	rebindKeygrabs(scr);
}

#endif /* !LITE */
