/*
 *		SETTINGS.C											vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *
 *		This module handles all the command parsing for tooltypes, command
 *		line options, config files, AREXX, etc.
 */		

#include "system.h"
#include "snoopdos.h"

extern char Version[];			/* From Snoopdos.c */
extern char CommodityTitle[];	/* From Snoopdos.c */

char ConfigID[] = "<SnoopDos Settings>";

ULONG			UpdateFlags;		/* Shows what settings were changed		*/
struct MsgPort *RemoteReplyPort;	/* Used when talking to b/g SnoopDos	*/

/*
 *		Now the result codes returned by our simple command interpreter
 */
typedef enum {
	EXEC_FAIL,			/* The command failed for some reason	*/
	EXEC_OKAY,			/* The command succeeded				*/
	EXEC_UNKNOWN,		/* The command wasn't recognised		*/
	EXEC_NOPARAM		/* The command was missing a parameter	*/
} ExecEnum;

/*
 *		Now let's define the commands we understand.
 */
#define CMD_UNKNOWN		-1
#define CMD_NOPARAM		-2

typedef enum {
	CMD_END,

	/*
	 *		Options within function requester. Note that these MUST correspond
	 *		exactly with the first set of enums in SNOOPDOS.H for GID_???!
	 */
	CMD_FINDPORT,
	CMD_FINDRESIDENT,
	CMD_FINDSEMAPHORE,
	CMD_FINDTASK,
	CMD_LOCKSCREEN,
	CMD_OPENDEVICE,
	CMD_OPENFONT,
	CMD_OPENLIBRARY,
	CMD_OPENRESOURCE,
	CMD_READTOOLTYPES,
	CMD_SENDREXX,

	CMD_CHANGEDIR,
	CMD_DELETE,
	CMD_EXECUTE,
	CMD_GETVAR,
	CMD_LOADSEG,
	CMD_LOCK,
	CMD_MAKEDIR,
	CMD_MAKELINK,
	CMD_OPEN,
  	CMD_RENAME,
  	CMD_RUNCOMMAND,
  	CMD_SETVAR,
	CMD_DOSSYSTEM,		/* CMD_SYSTEM is defined in dos/dosextens.h	*/

	CMD_DUMMY1,
	CMD_DUMMY2,
	CMD_DUMMY3,
	CMD_DUMMY4,
	CMD_DUMMY5,

	CMD_ONLYSHOWFAILS,
	CMD_SHOWCLI,
	CMD_SHOWFULLPATH,
	CMD_USEDEVICENAME,
	CMD_MONITORPACKETS,
	CMD_MONALLPACKETS,
	CMD_MONROMCALLS,
	CMD_IGNORESHELL,

#define MAX_BOOL_CMD			CMD_IGNORESHELL

	CMD_MATCHNAME,

	/*
	 *		General commands
	 */
	CMD_LOADSETTINGS,
	CMD_SAVESETTINGS,
	CMD_LOADDEFSETTINGS,
	CMD_SAVEDEFSETTINGS,
	CMD_SETTINGS,
	CMD_FUNCTIONS,
	CMD_LANGUAGE,
	CMD_CLEARBUFFER,
	CMD_WINDOWWIDTH,
	CMD_HELP,
	CMD_COPYWINDOW,
	CMD_COPYBUFFER,
	CMD_SAVEWINDOW,
	CMD_SAVEBUFFER,
	CMD_SHOW,
	CMD_HIDE,
	CMD_OPENFORMAT,
	CMD_OPENFUNCTION,
	CMD_OPENSETUP,
	CMD_CLOSEFORMAT,
	CMD_CLOSEFUNCTION,
	CMD_CLOSESETUP,
	CMD_QUIT,
	CMD_PAUSE,
	CMD_UNPAUSE,
	CMD_DISABLE,
	CMD_ENABLE,
	CMD_SINGLESTEP,
	CMD_OPENLOG,
	CMD_APPENDLOG,
	CMD_OPENSERIALLOG,
	CMD_CLOSELOG,
	CMD_ADDLOG,
	CMD_FLUSHLOG,
	CMD_SCROLLUP,
	CMD_SCROLLDOWN,
	CMD_GOTO,

	/*
	 *		General settings
	 */
	CMD_PATCHRAMLIB,
	CMD_STACKLIMIT,
	CMD_ICONPOS,
	CMD_SHOWGADGETS,
	CMD_HIDEGADGETS,
	CMD_AUTOOPEN,
	CMD_DISABLEWHENHIDDEN,
	CMD_SHOWSTATUS,
	CMD_HIDESTATUS,
	CMD_CREATEICONS,
	CMD_TEXTSPACING,
	CMD_SIMPLEREFRESH,
	CMD_SMARTREFRESH,
	CMD_LEFTALIGNED,
	CMD_RIGHTALIGNED,
	CMD_ROWQUALIFIER,
	CMD_MAINPOS,
	CMD_MAINSIZE,
	CMD_FUNCPOS,
	CMD_FORMPOS,
	CMD_SETPOS,
	CMD_TASKPRI,
	CMD_CXPRI,

	/*
	 *		Options within setup requester
	 */
	CMD_HIDEMETHOD,
	CMD_SCREENTYPE,
	CMD_BUFFERSIZE,
	CMD_LOGMODE,
	CMD_FILEIOTYPE,
	CMD_FORMAT,
	CMD_LOGFORMAT,
	CMD_HOTKEY,
	CMD_SCREENNAME,
	CMD_LOGNAME,
	CMD_WINDOWFONT,
	CMD_BUFFERFONT,

	NUMCOMMANDS

} COMMAND_ID;


/*
 *		Now associate a command string with each of these identifiers
 */
struct Command {
	short	cmdid;			/* Command ID							*/
	short	changemask;		/* Which group of settings will change	*/
	short	numparms;		/* Min number of parameters (0 or more)	*/
	char	*name;			/* Command name							*/
} CommandTable[] = {
	/*
	 *		In alphabetical order, for quick reference
	 */
	CMD_ADDLOG,				SET_NONE,	1, "AddLog",
	CMD_APPENDLOG,			SET_NONE,	1, "AppendLog",
	CMD_AUTOOPEN,			SET_MAIN,	0, "AutoOpen",
	CMD_BUFFERFONT, 		SET_SETUP, 	1, "BufferFont",
	CMD_BUFFERSIZE, 		SET_SETUP, 	1, "BufferSize",
	CMD_CHANGEDIR,			SET_FUNC,	0, "ChangeDir",
	CMD_CLEARBUFFER,		SET_FUNC,	0, "ClearBuffer",
	CMD_CLOSEFORMAT,		SET_NONE,	0, "CloseFormat",
	CMD_CLOSEFUNCTION,		SET_NONE,	0, "CloseFunction",
	CMD_CLOSELOG,			SET_NONE,	0, "CloseLog",
	CMD_CLOSESETUP, 		SET_NONE,	0, "CloseSetup",
	CMD_COPYBUFFER, 		SET_NONE,	0, "CopyBuffer",
	CMD_COPYWINDOW, 		SET_NONE,	0, "CopyWindow",
	CMD_CREATEICONS,		SET_MAIN,	0, "CreateIcons",
	CMD_HOTKEY,				SET_SETUP,	1, "CX_PopKey",
	CMD_SHOW,				SET_NONE,	0, "CX_Popup",
	CMD_CXPRI,				SET_NONE,	1, "CX_Priority",
	CMD_DELETE, 			SET_FUNC,	0, "Delete",
	CMD_DISABLE,			SET_NONE,	0, "Disable",
	CMD_DISABLEWHENHIDDEN,	SET_MAIN,	0, "DisableWhenHidden",
	CMD_ENABLE, 			SET_NONE,	0, "Enable",
	CMD_EXECUTE,			SET_FUNC,	0, "Execute",
	CMD_FILEIOTYPE, 		SET_SETUP,	1, "FileIOType",
	CMD_FINDPORT,			SET_FUNC,	0, "FindPort",
	CMD_FINDRESIDENT,		SET_FUNC,	0, "FindResident",
	CMD_FINDSEMAPHORE,		SET_FUNC,	0, "FindSemaphore",
	CMD_FINDTASK,			SET_FUNC,	0, "FindTask",
	CMD_FLUSHLOG,			SET_NONE,	0, "FlushLog",
	CMD_FORMAT, 			SET_SETUP,	1, "Format",
	CMD_FORMPOS,			SET_MAIN,	1, "FormatWindowPos",
	CMD_FUNCTIONS,			SET_FUNC,	1, "Functions",
	CMD_FUNCPOS,			SET_MAIN,	1, "FunctionWindowPos",
	CMD_GETVAR, 			SET_FUNC,	0, "GetVar",
	CMD_GOTO,				SET_NONE,	1, "GotoLine",
	CMD_HELP,				SET_NONE,	0, "Help",
	CMD_HIDE,				SET_NONE,	0, "Hide",
	CMD_HIDEGADGETS,		SET_MAIN,	0, "HideGadgets",
	CMD_HIDEMETHOD, 		SET_SETUP,	1, "HideMethod",
	CMD_HIDESTATUS, 		SET_MAIN,	0, "HideStatus",
	CMD_HOTKEY, 			SET_SETUP,	1, "HotKey",
	CMD_ICONPOS,			SET_MAIN,	1, "IconPos",
	CMD_IGNORESHELL,		SET_FUNC,	0, "IgnoreShell",
	CMD_LANGUAGE,			SET_NONE,	1, "Language",
	CMD_LEFTALIGNED,		SET_MAIN,	0, "LeftAligned",
	CMD_LOADDEFSETTINGS,	SET_NONE,	0, "LoadDefSettings",
	CMD_LOADSEG,			SET_FUNC,	0, "LoadSeg",
	CMD_LOADSETTINGS, 		SET_NONE,	1, "LoadSettings",
	CMD_LOCK,				SET_FUNC,	0, "Lock",
	CMD_LOCKSCREEN, 		SET_FUNC,	0, "LockScreen",
	CMD_LOGFORMAT,			SET_SETUP,	1, "LogFormat",
	CMD_LOGMODE,			SET_SETUP,	1, "LogMode",
	CMD_LOGNAME,			SET_SETUP,	1, "LogName",
	CMD_MAINPOS,			SET_MAIN,	1, "MainWindowPos",
	CMD_MAINSIZE,			SET_MAIN,	1, "MainWindowSize",
	CMD_MAKEDIR,			SET_FUNC,	0, "MakeDir",
	CMD_MAKELINK,			SET_FUNC,	0, "MakeLink",
	CMD_MATCHNAME,			SET_FUNC,	1, "MatchName",
	CMD_MONITORPACKETS, 	SET_FUNC,	0, "MonitorPackets",
	CMD_MONROMCALLS,		SET_FUNC,	0, "MonitorROMCalls",
	CMD_ONLYSHOWFAILS,		SET_FUNC,	0, "OnlyShowFails",
	CMD_OPEN,				SET_FUNC,	0, "Open",
	CMD_OPENDEVICE, 		SET_FUNC,	0, "OpenDevice",
	CMD_OPENFONT,			SET_FUNC,	0, "OpenFont",
	CMD_OPENFORMAT, 		SET_NONE,	0, "OpenFormat",
	CMD_OPENFUNCTION,		SET_NONE,	0, "OpenFunction",
	CMD_OPENLIBRARY,		SET_FUNC,	0, "OpenLibrary",
	CMD_OPENLOG,			SET_NONE,	1, "OpenLog",
	CMD_OPENRESOURCE,		SET_FUNC,	0, "OpenResource",
	CMD_OPENSERIALLOG,		SET_NONE,	0, "OpenSerialLog",
	CMD_OPENSETUP,			SET_NONE,	0, "OpenSetup",
	CMD_MONALLPACKETS,		SET_FUNC,	0, "PacketDebugger",
	CMD_PATCHRAMLIB,		SET_NONE,	0, "PatchRamLib",
	CMD_PAUSE,				SET_NONE,	0, "Pause",
	CMD_QUIT,				SET_NONE,	0, "Quit",
	CMD_READTOOLTYPES,		SET_FUNC,	0, "ReadToolTypes",
	CMD_RENAME, 			SET_FUNC,	0, "Rename",
	CMD_RIGHTALIGNED,		SET_MAIN,	0, "RightAligned",
	CMD_ROWQUALIFIER,		SET_MAIN,	1, "RowQualifier",
	CMD_RUNCOMMAND, 		SET_FUNC,	0, "RunCommand",
	CMD_SAVEBUFFER, 		SET_NONE,	1, "SaveBuffer",
	CMD_SAVEDEFSETTINGS,  	SET_NONE,	0, "SaveDefSettings",
	CMD_SAVESETTINGS, 		SET_NONE,	1, "SaveSettings",
	CMD_SAVEWINDOW, 		SET_NONE,	1, "SaveWindow",
	CMD_SCREENNAME, 		SET_SETUP,	1, "ScreenName",
	CMD_SCREENTYPE, 		SET_SETUP,	1, "ScreenType",
	CMD_SCROLLDOWN,			SET_NONE,	1, "ScrollDown",
	CMD_SCROLLUP,			SET_NONE,	1, "ScrollUp",
	CMD_SENDREXX,			SET_FUNC,	0, "SendRexx",
	CMD_SETTINGS,			SET_NONE,	1, "Settings",
	CMD_SETPOS, 			SET_MAIN,	1, "SetupWindowPos",
	CMD_SETVAR, 			SET_FUNC,	0, "SetVar",
	CMD_SHOW,				SET_NONE,	0, "Show",
	CMD_SHOWCLI,			SET_FUNC,	0, "ShowCLI",
	CMD_SHOWFULLPATH,		SET_FUNC,	0, "ShowFullPath",
	CMD_SHOWGADGETS,		SET_MAIN,	0, "ShowGadgets",
	CMD_SHOWSTATUS, 		SET_MAIN,	0, "ShowStatus",
	CMD_SIMPLEREFRESH,		SET_MAIN,	0, "SimpleRefresh",
	CMD_SINGLESTEP,			SET_NONE,	0, "SingleStep",
	CMD_SMARTREFRESH,		SET_MAIN,	0, "SmartRefresh",
	CMD_STACKLIMIT,			SET_MAIN,	1, "StackLimit",
	CMD_DOSSYSTEM,			SET_FUNC,	0, "System",
	CMD_TASKPRI,			SET_NONE,	1, "TaskPri",
	CMD_TEXTSPACING,		SET_MAIN,	1, "TextSpacing",
	CMD_UNPAUSE,			SET_NONE,	0, "Unpause",
	CMD_USEDEVICENAME,		SET_FUNC,	0, "UseDeviceNames",
	CMD_WINDOWFONT, 		SET_SETUP,	1, "WindowFont",
	CMD_WINDOWWIDTH,		SET_NONE,	1, "WindowWidth",
	CMD_END,				0,			0, NULL
};

#define NUM_CMDNAMES	(sizeof(CommandTable) / sizeof(CommandTable[0]))

char *CmdNames[NUMCOMMANDS];	/* Map command IDs back to name strings	*/

char *Names_HideMethod[] = { "Invisible", "Iconify",	"ToolsMenu",
							 "None",	   NULL };
char *Names_ScreenType[] = { "Default",   "Front",		"Named",		NULL };
char *Names_LogMode[]	 = { "Prompt",	  "Append",		"Overwrite",
						     "SerialPort", NULL };
char *Names_FileIOType[] = { "Automatic", "Immediate",	"Buffered",		NULL };

char *Names_Functions[]  = { "All", "None", "AllDos", "NoDos",
							 "AllSystem", "NoSystem", NULL };

char *Names_RowQualifier[] = { "Ignore", "None", "Shift", "Alt", "Ctrl",
							   "All",     NULL };

typedef enum {
	FUNC_ALL, FUNC_NONE, FUNC_ALLDOS, FUNC_NODOS,
	FUNC_ALLSYSTEM, FUNC_NOSYSTEM
} FuncTypes;

/*
 *		InitSettings()
 *
 *		Initialises variables associated with settings. For now, this
 *		just means the mapping array that maps command IDs to command
 *		names. This makes it easier to write out the configuration file.
 */
void InitSettings(void)
{
	struct Command *cmd;
	int i;

	for (i = 0; i < NUMCOMMANDS; i++)
		CmdNames[i] = "";	/* Make sure non-cmds initialised to safe value */

	for (cmd = CommandTable; cmd->cmdid != CMD_END; cmd++)
		CmdNames[cmd->cmdid] = cmd->name; 
}

/*
 *		MatchParam(&var, param, keywords)
 *
 *		Searches the keyword list for a match with param and stores
 *		the result in the given variable. If no match is found, var
 *		is not altered and returns EXEC_FAIL, else returns EXEC_OKAY.
 */
int MatchParam(unsigned char *var, char *param, char **keywords)
{
	int i;

	for (i = 0; keywords[i]; i++) {
		if (stricmp(param, keywords[i]) == 0) {
			*var = i;
			return (EXEC_OKAY);
		}
	}
	return (EXEC_FAIL);
}

/*
 *		ParseFontName(fontspec, fontname, &fontsize)
 *
 *		Parses the font spec in fontspec, which is of the form
 *		"fontname size" or "fontname.font size" or "fontname.size",
 *		into two parts.
 *
 *		The bulk of the name (plus a .font suffix) is copied into fontname.
 *		The size of the font is copied into fontsize. In the event of a parse
 *		error, EXEC_FAIL is returned and no alteration is made to fontname
 *		or fontsize. For success, EXEC_OKAY is returned.
 *
 *		Note that the contents of the string pointed to by fontspec may be
 *		altered. If fontspec is "default" then a fontsize of 0 is returned
 *		to indicate that SnoopDos should use the system default fonts.
 */
int ParseFontName(char *fontspec, char *fontname, USHORT *fontsize)
{
	char *p;
	int size;

	/*
	 *		First, see if we can find a dot specifier. Anything before
	 *		the dot is a font name.
	 */
	if (stricmp(fontspec, "default") == 0) {
		strcpy(fontname, fontspec);
		*fontsize = 0;
		return (EXEC_OKAY);
	}
	p = strchr(fontspec, '.');
	if (p) {
		*p++ = '\0';
		if (strnicmp(p, "font", 4) == 0)
			p += 4;
	} else {
		p = strchr(fontspec, ' ');
		if (!p)
			return (EXEC_FAIL);
		*p++ = '\0';
	}
	while (*p == ' ' || *p == '.')
		p++;
	size = atoi(p);
	if (size == 0)
		return (EXEC_FAIL);
	
	strcpy(fontname, fontspec);
	strcat(fontname, ".font");
	*fontsize = size;
	return (EXEC_OKAY);
}

/*
 *		ParseTwoNums(param, &num1, &num2)
 *
 *		Parses the given param string, assumed to be in the form "x1,x2"
 *		and stores the two numbers in the given variables. If either number
 *		is invalid, then no change is made to either number and EXEC_FAIL
 *		is returned, else EXEC_OKAY is returned.
 *
 *		The string pointed to by param may be altered.
 */
int ParseTwoNums(char *param, WORD *num1, WORD *num2)
{
	char *p;

	p = strchr(param, ',');
	if (!p)
		return (EXEC_FAIL);
	*p++ = '\0';

	while (*p == ' ')
		p++;
	
	if (*param < '-' || *param > '9' || *p < '-' || *p > '9')
		return (EXEC_FAIL);
	
	*num1 = atoi(param);
	*num2 = atoi(p);
	return (EXEC_OKAY);
}

/*
 *		cid = ParseCommand(cmdline, cmdname, param, &boolvalue, &cmd)
 *
 *		Copies the cmdline into two separate strings, one for the
 *		command name and one for the parameters. Bool is initalised
 *		to 0 or 1, according to the default value of the parameter
 *		(1 if Yes,True,On, 0 if No, False, Off, or if the command
 *		is prefixed by the word No).
 *
 *		cmd points to the command structure for this command.
 *
 *		cid is the command ID that matches the command, CMD_END for
 *		a null command, CMD_UNKNOWN for an unrecognised command,
 *		and CMD_NOPARAM for a known command with too few parameters.
 *		If CMD_NOPARAM is returned, cmdname and &cmd will be initialised
 *		to the command that was matched.
 *
 */
int ParseCommand(char *cmdline, char *cmdname, char *param, int *boolvalue,
				 struct Command **pcmd)
{
	struct Command *cmd;
	int cid;
	char *p;
	char *q;

	if (!cmdline)
		return (CMD_END);

	/*
	 *		First, copy just the command header
	 */
	for (p = cmdline; *p == ' ' || *p == '\t'; p++)
		;
	q = cmdname;
	while (*p        && *p != ';'  && *p != '='  &&
		   *p != ' ' && *p != '\t' && *p != '\n')
		*q++ = *p++;
	*q = '\0';

	if (!*cmdname)
		return (CMD_END);	/* Skip over blank lines and comments */

	/*
	 *		Now skip over any whitespace until we find the next parameter
	 */
	while (*p == ' ' || *p == '=' || *p == '\t' || *p == '\n')
		p++;
	q = param;
	if (*p == '\"') {
		/*
		 *		Got a quoted string .. copy everything inside the quotes
		 */
		p++;
		while (*p && *p != '\n' && *p != '\"')
			*q++ = *p++;
		*q = '\0';
	} else {
		/*
		 *		Not a quoted string .. copy everything to end of line
		 *		or semicolon, excluding any trailing spaces.
		 */
		while (*p && *p != '\n' && *p != ';')
			*q++ = *p++;
		while (--q >= param && (*q == ' ' || *q == '\n' || *q == '\t'))
			;
		*++q = '\0';
		if (q == param)
			q = NULL;
	}

	/*
	 *		Finally, if we got no specific parameter, check if the
	 *		command beings with "No" -- if so, then assume the
	 *		parameter is boolean instead and set accordingly
	 */
	*boolvalue = 1;
	if (!q) {
		if (strnicmp(cmdname, "no", 2) == 0) {
			*boolvalue = 0;
			cmdname   += 2;
		}
	} else {
		if (	stricmp(param, "off") == 0	||
				stricmp(param, "no")  == 0	||
				stricmp(param, "0")   == 0)
		{
			*boolvalue = 0;
		}
	}

	/*
	 *		Now locate identifier which matches command
	 */
	for (cmd = CommandTable; cmd->cmdid != CMD_END; cmd++)
		if (stricmp(cmd->name, cmdname) == 0)
			break;
	
	cid = cmd->cmdid;
	if (cid == CMD_END)
		return (CMD_UNKNOWN);

	*pcmd = cmd;
	if (cmd->numparms > 0 && !q)
		return (CMD_NOPARAM);
	
	return (cid);
}

/*
 *		ExecCommand(cmd, mode, set)
 *
 *		Interprets the given command and executes it. cmd points to the
 *		command string which is one of the following forms:
 *
 *			command
 *			command=value
 *			setting
 *			nosetting
 *			setting=on
 *			setting=off
 *	
 *		The "setting" commands are used to toggle the various boolean
 *		settings that can be accessed through the requesters.
 *
 *		mode is the place where the setting occurs. This will be one of
 *		MODE_REXX, MODE_SETTINGS, MODE_CMDLINE or MODE_TOOLTYPE, depending
 *		on where the command being executed originated from. In some cases,
 *		this affects how the command is executed.
 *
 *		set is a pointer to a copy of the current settings (NOT the actual
 *		settings!) This copy is updated to reflect the changes. The intention
 *		is that you can call ExecCommand() multiple times with the same
 *		copy, and then finally call InstallSettings() at the end to make
 *		the new settings take effect. This way, only a single update
 *		needs to be done for an entire configuration file.
 *
 *		The global UpdateFlags should be initialised to 0 before the first
 *		call, and then passed to InstallSettings() at the end -- it will
 *		contain the appropriate combination of SET_??? flags to indicate
 *		what settings actually changed.
 *
 *		Returns EXEC_OKAY for success, EXEC_FAIL for failure, or EXEC_UNKNOWN
 *		if the command couldn't be understood.
 */
int ExecCommand(char *cmdline, int mode, Settings *set)
{
	APTR oldwinptr = *TaskWindowPtr;
	struct Command *cmd;		/* Pointer to command						*/
	char cmdname[100];			/* Storage area for command 				*/
	char parambuf[100];			/* Storage for parameters					*/
	char *param;				/* Pointer to parameters					*/
	int	 boolvalue;				/* for settings: either 1 or 0				*/
	int  success = EXEC_OKAY;	/* Default return value						*/
	int  cid;
	int  val;

	cid = ParseCommand(cmdline, cmdname, parambuf, &boolvalue, &cmd);
	switch (cid) {
		case CMD_END:		return (EXEC_OKAY);
		case CMD_UNKNOWN:	return (EXEC_UNKNOWN);
		case CMD_NOPARAM:	return (EXEC_NOPARAM);
	}

	UpdateFlags |= cmd->changemask;
	param = parambuf;
	
	/*
	 *		Okay, got ourselves a valid command. Now interpret it.
	 */
	if (cid <= MAX_BOOL_CMD) {
		/*
		 *		Got a boolean option to update
		 */
		set->Func.Opts[cid] = boolvalue;
		return (EXEC_OKAY);
	}

	switch (cid) {
		case CMD_LANGUAGE:
			strcpy(Language, param);
			break;

		case CMD_SETTINGS:
			strcpy(DefaultConfigName, param);
			strcpy(ConfigFileName, param);
			break;

		case CMD_LOADDEFSETTINGS:
			success = LoadConfig(DefaultConfigName, mode, set);
			break;

		case CMD_SAVEDEFSETTINGS:
			success = SaveConfig(DefaultConfigName, SAVE_NOICON);
			break;

		case CMD_LOADSETTINGS:
			success = LoadConfig(param, mode, set);
			break;

		case CMD_SAVESETTINGS:
			success = SaveConfig(param, SAVE_ICON);
			break;

		case CMD_HELP:
		{
			char helpmsg[100];

			/*
			 *		We install the current settings first, to ensure
			 *		that things like the default public screen are
			 *		correctly set up before we open help.
			 */
			if (UpdateFlags) {
				InstallSettings(set, UpdateFlags);
				UpdateFlags = 0;
				*set = CurSettings;
			}
			if (*param)
				mysprintf(helpmsg, "LINK %s\n", param);
			else
				strcpy(helpmsg, MSG(MSG_LINK_MAIN));
			ShowAGuide(helpmsg);
			break;
		}

		case CMD_SHOWGADGETS:	set->ShowGadgets   =  boolvalue;	break;
		case CMD_HIDEGADGETS:	set->ShowGadgets   = !boolvalue;	break;
		case CMD_SHOWSTATUS:	set->ShowStatus    =  boolvalue;	break;
		case CMD_HIDESTATUS:	set->ShowStatus	   = !boolvalue;	break;
		case CMD_AUTOOPEN:		set->AutoOpenMain  =  boolvalue;	break;
		case CMD_DISABLEWHENHIDDEN: set->DisableWhenHidden = boolvalue; break;
		case CMD_CREATEICONS:	set->MakeIcons     =  boolvalue;	break;
		case CMD_SIMPLEREFRESH:	set->SimpleRefresh =  boolvalue;	break;
		case CMD_SMARTREFRESH:	set->SimpleRefresh = !boolvalue;	break;
		case CMD_LEFTALIGNED:	set->RightAlign    = !boolvalue;	break;
		case CMD_RIGHTALIGNED:	set->RightAlign    =  boolvalue;	break;
		case CMD_TEXTSPACING:	
			val = *param - '0';
			if (val < 0 || val > 2)
				return (EXEC_FAIL);
			set->TextSpacing = val;
			break;

		case CMD_ROWQUALIFIER:
			return MatchParam(&set->RowQualifier, param, Names_RowQualifier);

		case CMD_FUNCTIONS:
			/*
			 *		Set or clear the function table according to the
			 *		parameters
			 */
		{
			unsigned char functype;
			int low  = FIRST_SYS_GADGET;
			int high = LAST_DOS_GADGET;
			int bool = 1;
			int i;

			if (MatchParam(&functype, param, Names_Functions) == EXEC_FAIL)
				return (EXEC_FAIL);

			switch (functype) {
				case FUNC_ALL:		 break;				/* Use defaults */
				case FUNC_NONE:		 bool = 0; break;
				case FUNC_ALLDOS:	 low  = FIRST_DOS_GADGET;			break;
				case FUNC_NODOS:	 low  = FIRST_DOS_GADGET; bool = 0; break;
				case FUNC_ALLSYSTEM: high = LAST_SYS_GADGET;			break;
				case FUNC_NOSYSTEM:	 high = LAST_SYS_GADGET;  bool = 0; break;
			}
			for (i = low; i <= high; i++)
				set->Func.Opts[i] = bool;
			break;
		}

		case CMD_COPYWINDOW:
			DisableAllWindows();
			success = SaveBuffer(SAVEBUF_WINDOW, SAVEBUF_CLIPBOARD,
								 SAVEBUF_OVERWRITE);
			EnableAllWindows();
			break;
								/* Fallthrough */
		case CMD_COPYBUFFER:
			DisableAllWindows();
			success = SaveBuffer(SAVEBUF_ALL, SAVEBUF_CLIPBOARD,
								 SAVEBUF_OVERWRITE);
			EnableAllWindows();
			break;

		case CMD_SAVEWINDOW:
			DisableAllWindows();
			success = SaveBuffer(SAVEBUF_WINDOW, param, SAVEBUF_OVERWRITE);
			EnableAllWindows();
			break;

		case CMD_SAVEBUFFER:
			DisableAllWindows();
			success = SaveBuffer(SAVEBUF_ALL, param, SAVEBUF_OVERWRITE);
			EnableAllWindows();
			break;
			
		case CMD_CXPRI:
			CommodityPriority = atoi(param);
			if (HotKeyActive) {
				/*
				 *		Reinstall commodity to take advantage of the
				 *		updated priority
				 */
				InstallHotKey(CurSettings.Setup.HotKey);
			}
			break;

		case CMD_ICONPOS:
			success = ParseTwoNums(param, &set->IconPosLeft, &set->IconPosTop);
			break;

		case CMD_HIDE:
		case CMD_SHOW:
			/*
			 *		Install all settings modified so far
			 */
			if (UpdateFlags) {
				InstallSettings(set, UpdateFlags);
				UpdateFlags = 0;
				*set = CurSettings;
			}
			/*
			 *		We allow both of these so that NOSHOW and NOHIDE
			 *		will also work, as well as SHOW=YES and HIDE=NO
			 *		In particular, CX_Popup=Yes and CX_Popup=No will
			 *		work as expected.
			 */
			if ((boolvalue && cid == CMD_SHOW) ||
										(!boolvalue && cid == CMD_HIDE)) {
				/*
				 *		Showing SnoopDos. Install all settings changed
				 *		so far, so that they'll be in effect when the window
				 *		is open (especially font changes)
				 */
				success = ShowSnoopDos();
				HideOnStartup = 0;
			} else {
				HideSnoopDos();
				HideOnStartup = 1;
			}
			break;

		case CMD_OPENFORMAT:
		case CMD_OPENFUNCTION:
		case CMD_OPENSETUP:
				/*
				 *		Install the current settings, so that they take
				 *		effect before the window is opened (font changes
				 *		and the like).
				 */
				if (UpdateFlags) {
					InstallSettings(set, UpdateFlags);
					UpdateFlags = 0;
					*set = CurSettings;
				}
				switch (cid) {
					case CMD_OPENFORMAT:	success = OpenFormatWindow();
											break;
					case CMD_OPENFUNCTION:	success = OpenFunctionWindow();
											break;
					case CMD_OPENSETUP:		success = OpenSettingsWindow();
											break;
				}
				break;

		case CMD_CLOSEFORMAT:		CloseFormatWindow();			break;
		case CMD_CLOSEFUNCTION:		CloseFunctionWindow();			break;
		case CMD_CLOSESETUP:		CloseSettingsWindow();			break;

		case CMD_QUIT:
			if (mode != MODE_SETTINGS)
				QuitFlag = 1;
			else
				success = EXEC_FAIL;
			break;
			
		case CMD_SINGLESTEP:		SingleStep();						break;

		case CMD_PAUSE:
		case CMD_DISABLE:
		case CMD_UNPAUSE:
		case CMD_ENABLE:
			/*
			 *		We treat these four together so we can account for
			 *		all the different boolean verisons (Disable=Yes,
			 *		Enable=No, UnPause=Yes, Pause=No, etc.)
			 */
			if ((cid == CMD_DISABLE && boolvalue) ||
				(cid == CMD_ENABLE && !boolvalue))
			{
				SetMonitorMode(MONITOR_DISABLED);
			} else if ((cid == CMD_PAUSE && boolvalue) ||
					   (cid == CMD_UNPAUSE && !boolvalue))
			{
				SetMonitorMode(MONITOR_PAUSED);
			} else
				SetMonitorMode(MONITOR_NORMAL);
			break;

		case CMD_OPENLOG:
		case CMD_APPENDLOG:
			/*
			 *		For these two, we make any current settings take effect,
			 *		so that things like default file buffering mode will be
			 *		activated.
			 */
			if (UpdateFlags) {
				InstallSettings(set, UpdateFlags);
				UpdateFlags = 0;
				*set = CurSettings;
			}
			if (cid == CMD_OPENLOG)
				success = OpenLog(LOGMODE_OVERWRITE, param);
			else
				success = OpenLog(LOGMODE_APPEND, param);
			break;

		case CMD_OPENSERIALLOG:
			success = OpenLog(LOGMODE_SERIALPORT, param);
			break;

		case CMD_CLOSELOG:
			CloseLog();
			break;

		case CMD_ADDLOG:
			/*
			 *		Output a user comment to the specified logfile
			 */
			strcat(param, "\n");
			WriteLog(param);
			break;

		case CMD_FLUSHLOG:
			/*
			 *		Flush the current contents of the log
			 */
			WriteLog(NULL);
			break;

		case CMD_HIDEMETHOD: return MatchParam(&set->Setup.HideMethod,	param,
											   Names_HideMethod);
		case CMD_SCREENTYPE: return MatchParam(&set->Setup.ScreenType,	param,
											   Names_ScreenType);
		case CMD_LOGMODE:	 return MatchParam(&set->Setup.LogMode,		param,
											   Names_LogMode);
		case CMD_FILEIOTYPE: return MatchParam(&set->Setup.FileIOType,	param,
											   Names_FileIOType);

		case CMD_BUFFERSIZE:
			val = atoi(param);
			if (val > 1)
				set->Setup.BufferSize = val;
			break;

		case CMD_WINDOWWIDTH:
			SetMainWindowWidth(atoi(param));	/* 0 is supported */
			break;

		case CMD_STACKLIMIT:
			if (*param < '0' || *param > '9')
				return (EXEC_FAIL);
			set->StackLimit = atoi(param);
			break;

		case CMD_PATCHRAMLIB:	break;	/* Actually handled in ParseStartup */

		case CMD_FORMAT:		strcpy(set->Setup.BufferFormat,  param); break;
		case CMD_LOGFORMAT:
			if (stricmp(param, "none") == 0)
				*param = '\0';
			strcpy(set->Setup.LogfileFormat, param); break;
			break;

		case CMD_HOTKEY:		strcpy(set->Setup.HotKey,	 	 param); break;
		case CMD_SCREENNAME:	strcpy(set->Setup.ScreenName,	 param); break;
		case CMD_LOGNAME:		strcpy(set->Setup.LogFile,		 param);
								*TaskWindowPtr = (APTR)-1;
								if (IsFileSystem(set->Setup.LogFile))
									strcpy(ChosenLogName, param);
								*TaskWindowPtr = oldwinptr;
								break;

		case CMD_MATCHNAME:  	strcpy(set->Func.Pattern,		 param); break;
		case CMD_WINDOWFONT:
			return ParseFontName(param, set->Setup.WindowFont,
										&set->Setup.WinFontSize);
		case CMD_BUFFERFONT:
			return ParseFontName(param, set->Setup.BufferFont,
										&set->Setup.BufFontSize);

		case CMD_MAINSIZE:
			success = ParseTwoNums(param, &set->MainWinWidth,
									      &set->MainWinHeight);
			if (success && mode != MODE_SETTINGS && MainWindow)
				SizeWindow(MainWindow, set->MainWinWidth - MainWindow->Width,
									   set->MainWinHeight- MainWindow->Height);
			break;

		case CMD_MAINPOS:
			success = ParseTwoNums(param, &set->MainWinLeft, &set->MainWinTop);
			if (success && mode != MODE_SETTINGS && MainWindow)
				MoveWindow(MainWindow, set->MainWinLeft - MainWindow->LeftEdge,
									   set->MainWinTop  - MainWindow->TopEdge);
			break;

		case CMD_FORMPOS:
			success = ParseTwoNums(param, &set->FormWinLeft, &set->FormWinTop);
			if (success && mode != MODE_SETTINGS && FormWindow)
				MoveWindow(FormWindow, set->FormWinLeft - FormWindow->LeftEdge,
									   set->FormWinTop  - FormWindow->TopEdge);
			break;

		case CMD_FUNCPOS:
			success = ParseTwoNums(param, &set->FuncWinLeft, &set->FuncWinTop);
			if (success && mode != MODE_SETTINGS && FuncWindow)
				MoveWindow(FuncWindow, set->FuncWinLeft - FuncWindow->LeftEdge,
									   set->FuncWinTop  - FuncWindow->TopEdge);
			break;

		case CMD_SETPOS:
			success = ParseTwoNums(param, &set->SetupWinLeft,
										  &set->SetupWinTop);
			if (success && mode != MODE_SETTINGS && SetWindow)
				MoveWindow(SetWindow, set->SetupWinLeft - SetWindow->LeftEdge,
									  set->SetupWinTop  - SetWindow->TopEdge);
			break;

		case CMD_TASKPRI:
			val = atoi(param);
			if ((val == 0 && *param != '0') || val < -20 || val > 20)
				return (EXEC_FAIL);
			SetTaskPri(SysBase->ThisTask, val);
			if (MainWindow)
				SetMenuOptions();
			break;

		case CMD_CLEARBUFFER:
			ClearWindowBuffer();
			break;

		case CMD_GOTO:
			val = atoi(param);
			if (val != 0) {
				val += BaseSeq;		/* Get real start position */
				if (val > TopSeq)
					DoArrowScrolling(GID_DOWNARROW, val - TopSeq);
				else
					DoArrowScrolling(GID_UPARROW, TopSeq - val);
			} else
				success = EXEC_FAIL;
			break;

		case CMD_SCROLLUP:
		case CMD_SCROLLDOWN:
			val = atoi(param);
			if (!val)
				val = 1;
			if (cid == CMD_SCROLLUP)
				DoArrowScrolling(GID_UPARROW, val);
			else
				DoArrowScrolling(GID_DOWNARROW, val);
			break;
	}
	return (success ? EXEC_OKAY : EXEC_FAIL);
}

/*
 *		GetFontDesc(fontdesc, fontname, size)
 *
 *		Builds a a string from the supplied fontname and size and stores
 *		it in fontdesc. Returns a pointer to the new string.
 *
 *		For example, name="courier.font" and size=13 will return the
 *		string "courier 13";
 *
 *		In the event that the fontname is invalid, the string "Default"
 *		is returned instead.
 */
char *GetFontDesc(char *fontdesc, char *fontname, int size)
{
	char *p;

	if (!fontname || !*fontname)
		return ("Default");

	strcpy(fontdesc, fontname);
	p = strchr(fontdesc, '.');
	if (!p)
		p = fontdesc + strlen(fontdesc);
	
	mysprintf(p, " %ld", size);
	return (fontdesc);
}

/*
 *		SaveConfig(filename, saveicon)
 *
 *		Writes the current configuration to the named disk file. Returns
 *		1 for success, 0 for failure.
 *
 *		saveicon is SAVE_ICON if an icon should be saved and CreateIcos
 *		is true, or SAVE_NOICON if an icon should never be saved.
 */
int SaveConfig(char *filename, int saveicon)
{
	char tempdesc[MAX_SHORT_LEN];
	SetupSettings *setup = &CurSettings.Setup;
	BPTR file;
	int i;

	mysprintf(StatusLineText, MSG(MSG_STATUS_SAVESET), filename);
	ShowStatus(StatusLineText);

	DisableAllWindows();
	RecordWindowSizes();
	file = Open(filename, MODE_NEWFILE);
	if (!file) {
		EnableAllWindows();
		UpdateStatus();
		return (0);
	}
	FPrintf(file, "%s\n;\n;    %s\n;\n;    Settings file\n;\n",
			ConfigID, Version);

#define NM(x)			CmdNames[CMD_##x]
#define PF1(s,p)		FPrintf(file, s, p)
#define PF2(s,p1,p2)	FPrintf(file, s, p1, p2)

	/*
	 *		First of all, write out the function settings
	 */
	for (i = 1; i <= MAX_BOOL_CMD; i++) {
		if (*CmdNames[i]) {
			if (CurSettings.Func.Opts[i])
				PF1("%s\n",	CmdNames[i]);
			else
				PF1("No%s\n", CmdNames[i]);
		}
	}
	/*
	 *		Now write out all the variable settings
	 */
	PF2(";\n%s=\"%s\"\n",NM(MATCHNAME),	CurSettings.Func.Pattern);
	PF2("%s=%s\n",	 	NM(HIDEMETHOD), Names_HideMethod[setup->HideMethod]);
	PF2("%s=%s\n",	 	NM(SCREENTYPE), Names_ScreenType[setup->ScreenType]);
	PF2("%s=%s\n",	 	NM(LOGMODE),  	Names_LogMode[setup->LogMode]);
	PF2("%s=%s\n",	 	NM(FILEIOTYPE), Names_FileIOType[setup->FileIOType]);
	PF2("%s=%ld\n",	 	NM(BUFFERSIZE), setup->BufferSize);
	PF2("%s=\"%s\"\n",	NM(HOTKEY),	  	setup->HotKey);
	PF2("%s=\"%s\"\n",	NM(SCREENNAME), setup->ScreenName);
	PF2("%s=\"%s\"\n",	NM(LOGNAME),	setup->LogFile);
	PF2("%s=\"%s\"\n",	NM(WINDOWFONT), GetFontDesc(tempdesc,
													setup->WindowFont,
												 	setup->WinFontSize));
	PF2("%s=\"%s\"\n",	NM(BUFFERFONT), GetFontDesc(tempdesc,
													setup->BufferFont,
												 	setup->BufFontSize));
	PF2("%s=\"%s\"\n",	NM(FORMAT),     setup->BufferFormat);
	PF2("%s=\"%s\"\n",	NM(LOGFORMAT),  setup->LogfileFormat);

	PF2("%s=%ld\n",		NM(TEXTSPACING), CurSettings.TextSpacing);

	PF1("%s\n",		 	CurSettings.SimpleRefresh ? NM(SIMPLEREFRESH) :
												    NM(SMARTREFRESH));
	PF1("%s\n",		 	CurSettings.RightAlign    ? NM(RIGHTALIGNED) :
												    NM(LEFTALIGNED));
	PF2("%s=%s\n",      NM(ROWQUALIFIER), Names_RowQualifier[RowQual]);
	PF1("%s\n",		 	CurSettings.ShowStatus    ? NM(SHOWSTATUS)	  :
												    NM(HIDESTATUS));
	PF1("%s\n",		 	CurSettings.ShowGadgets   ? NM(SHOWGADGETS)   :
												    NM(HIDEGADGETS));
	PF2("%s%s\n",		(AutoOpen ? "" : "No"), NM(AUTOOPEN));
	PF2("%s%s\n",       (DisableOnHide ? "" : "No"), NM(DISABLEWHENHIDDEN));
	PF2("%s%s\n",		(CurSettings.MakeIcons ? "" : "No"), NM(CREATEICONS));
	
	FPrintf(file, "%s=%ld,%ld\n", NM(MAINPOS),	CurSettings.MainWinLeft,
											  	CurSettings.MainWinTop);
	FPrintf(file, "%s=%ld,%ld\n", NM(MAINSIZE),	CurSettings.MainWinWidth,
											  	CurSettings.MainWinHeight);
	FPrintf(file, "%s=%ld,%ld\n", NM(FORMPOS),	CurSettings.FormWinLeft,
											  	CurSettings.FormWinTop);
	FPrintf(file, "%s=%ld,%ld\n", NM(FUNCPOS),	CurSettings.FuncWinLeft,
											  	CurSettings.FuncWinTop);
	FPrintf(file, "%s=%ld,%ld\n", NM(SETPOS),	CurSettings.SetupWinLeft,
											  	CurSettings.SetupWinTop);
	FPrintf(file, "%s=%ld,%ld\n", NM(ICONPOS),  CurSettings.IconPosLeft,
												CurSettings.IconPosTop);
	FPrintf(file, "%s=%ld\n",     NM(STACKLIMIT), CurSettings.StackLimit);
	FPrintf(file, "%s=%ld\n",     NM(TASKPRI),
								  SysBase->ThisTask->tc_Node.ln_Pri);
	Close(file);
	if (CreateIcons && saveicon != SAVE_NOICON)
		WriteIcon(filename);
	EnableAllWindows();
	UpdateStatus();
	GotLastSaved = 1;		/* Indicate "Last Saved" can now be performed */
	return (1);
}

/*
 *		LoadConfig(filename, mode, set)
 *
 *		Attempts to load the specified configuration file into memory.
 *		Returns 1 for success (having executed all the commands, and updated
 *		windows etc. as appropriate) or 0 for failure (couldn't open file).
 *
 *		To prevent infinite loops, we only allow three levels of nested
 *		configuration files (i.e. a maximum of three recursive calls to
 *		LoadConfig)
 *
 *		Normally, set is NULL. If non-NULL, then it represents a set
 *		of settings currently being updated. In this case, LoadConfig
 *		doesn't actually install the new settings when it's finished
 *		reading them, it merely updates the values in set with the
 *		new values and relies on the caller to set them later on.
 *
 *		The mode parameter indicates how we are being called -- from
 *		the CLI, Workbench, ARexx or internally. This is used to determine
 *		how error messages should be displayed (currently, messages will
 *		only be displayed in a CLI window).
 *
 *		New: we now support reading from an interactive file (CON:....)
 *		This allows the user to open a command window by trying to load
 *		a suitable CON: specification. Commands will be executed as soon
 *		as they arrive, and settings will be updated immediately.
 */
int LoadConfig(char *filename, int mode, Settings *set)
{
	static int nestcount;

	Settings newsettings;
	Settings *myset;
	int  interactive;
	int  linenum   = 0;
	int  retvalue  = EXEC_FAIL;
	BPTR errorfile = NULL;
	BPTR file;
	char linebuf[200];

	if (set == NULL) {
		myset  		= &newsettings;
		*myset		= CurSettings;
		UpdateFlags = 0;
	} else {
		myset = set;
	}
	if (nestcount >= MAX_LOAD_NESTING)
		return (EXEC_FAIL);
	nestcount++;
	
	DisableAllWindows();
	mysprintf(StatusLineText, MSG(MSG_STATUS_LOADSET), filename);
	ShowStatus(StatusLineText);

	file = Open(filename, MODE_OLDFILE);
	if (!file)
		goto abort_load;

	/*
	 *		If we're executing this command from a CLI, then display
	 *		error messages in the CLI window, else don't display them
	 *		at all.
	 */
	if (mode == MODE_CMDLINE)
		errorfile = Output();

	interactive = IsInteractive(file);
	if (interactive) {
		errorfile = file;	/* Send errors to the command window */
	} else {
		/*
		 *		If we're not loading settings from an interactive
		 *		window, then check that the first line of the file
		 *		contains the settings file identifier
		 */
		if (!FGets(file, linebuf, 199)							||
			strnicmp(linebuf, ConfigID, strlen(ConfigID)) != 0)
		{
			goto abort_load;
		}
		linenum++;
	}

	/*
	 *		Now read in lines from the command file and execute them.
	 */
	if (interactive) {
		FPrintf(errorfile, "%s%s", MSG(MSG_CMD_HEADER), MSG(MSG_CMD_PROMPT));
		Flush(errorfile);
	}
	while (FGets(file, linebuf, 199) != NULL) {
		char *cmdline = linebuf;
		char *q;
		int result;

		linenum++;
		for (q = cmdline; *q && *q != '\n'; q++)
			;
		if (*q)
			*q++ = '\0';
	
		while (*cmdline == ' ' || *cmdline == '\t')
			cmdline++;

		if (!*cmdline || *cmdline == ';')
			continue;	/* Skip over lines with no text on them */

		if (stricmp(cmdline, "exit") == 0)	/* Skip rest of file */
			break;

		if (interactive && *cmdline == '?') {
			ShowCommands(errorfile);
			goto show_prompt;
		}
			
		/*
		 *		Now execute the command ... interactive commands are
		 *		executed internally, to allow QUIT to be executed.
		 */
		result = ExecCommand(cmdline,
							 (interactive ? MODE_INTERNAL : MODE_SETTINGS),
							 myset);
		if (QuitFlag)
			break;

		if (result == EXEC_OKAY) {
			if (interactive) {
				/*
				 *		For interactive commands, we update the
				 *		settings at each step along the way so
				 *		the user can see them taking effect.
				 */
				InstallSettings(myset, UpdateFlags);
				UpdateFlags = 0;
				*myset = CurSettings;
			}
		} else if (errorfile) {
			int msgid;

			switch (result) {
				case EXEC_UNKNOWN:	msgid = MSG_CMD_UNKNOWN;	break;
				case EXEC_FAIL:		msgid = MSG_CMD_FAIL;		break;
				case EXEC_NOPARAM:	msgid = MSG_CMD_NOPARAM;	break;
			}
			if (!interactive)
				FPrintf(errorfile, "%s, line %ld: ", filename, linenum);
			FPrintf(errorfile, MSG(msgid), cmdline);
		}

show_prompt:
		if (interactive) {
			FPrintf(errorfile, MSG(MSG_CMD_PROMPT));
			Flush(errorfile);
		}
	}
	retvalue = EXEC_OKAY;

	if (interactive) {
		/*
		 *		For interactive use, we clear any CTRL-C typed by
		 *		the user during usage; this avoids confusion since
		 *		the CTRL-C wouldn't have taken effect immediately
		 *		anyway.
		 */
		CheckSignal(SIGBREAKF_CTRL_C);
	}
	/*
	 *		If we just successfully loaded a file, it was probably the
	 *		defaults file, so enable the "Last Saved" menu option (if it's
	 *		not enabled, then it doesn't try to load a file since there was
	 *		no file ever saved).
	 */
	GotLastSaved = 1;

abort_load:
	if (file)
		Close(file);
	nestcount--;
	if (set == NULL)
		InstallSettings(myset, UpdateFlags);

	UpdateStatus();
	EnableAllWindows();
	return (retvalue);
}

/*
 *		InitRexxPort
 *
 *		Attempts to create SnoopDos's public port and initialise SnoopPort
 *		and RexxPortMask accordingly. If the port already exists, still
 *		returns TRUE but SnoopPort will be NULL.
 *
 *		If we can't create the port for some reason, returns FALSE.
 */		
int InitRexxPort(void)
{
	struct MsgPort *port;

	Forbid();
	port = FindPort(PORT_NAME);
	if (!port) {
		SnoopPort = CreateMsgPort();
		if (!SnoopPort) {
			Permit();
			return (FALSE);
		}
		SnoopPort->mp_Node.ln_Name = PORT_NAME;
		SnoopPort->mp_Node.ln_Pri  = 1;	/* Speed up searches */
		AddPort(SnoopPort);
		RexxPortMask = 1 << SnoopPort->mp_SigBit;
	}
	Permit();
	return (TRUE);
}

/*
 *		CleanupRexxPort()
 *
 *		Cleans up our SnoopDos port -- removes it from the public port
 *		list, and then replies to any messages it has outstanding. Call
 *		before exiting.
 */
void CleanupRexxPort(void)
{
	if (SnoopPort) {
		struct RexxMsg *msg;

		Forbid();
		RemPort(SnoopPort);
		while ((msg = (struct RexxMsg *)GetMsg(SnoopPort)) != NULL) {
			msg->rm_Result1 = RC_FATAL;
			msg->rm_Result2 = NULL;
			ReplyMsg(msg);
		}
		Permit();
		DeleteMsgPort(SnoopPort);
		SnoopPort = NULL;
	}
	if (RemoteReplyPort) {
		DeleteMsgPort(RemoteReplyPort);
		RemoteReplyPort = NULL;
	}
}

/*
 *		HandleRexxMsgs()
 *
 *		Handles any new messages at our Rexx port
 */
void HandleRexxMsgs(void)
{
	Settings newsettings = CurSettings;
	struct RexxMsg *msg;

	UpdateFlags = 0;
	while ((msg = (struct RexxMsg *)GetMsg(SnoopPort)) != NULL) {
		msg->rm_Result1 = RC_OK;
		msg->rm_Result2 = NULL;
		if ((msg->rm_Action & RXCODEMASK) == RXCOMM) {
			/*
			 *		Got a valid ARexx message, now process it
			 */
			switch (ExecCommand(msg->rm_Args[0], MODE_REXX, &newsettings)) {
				/*
				 *		See include:rexx/errors.h for the meanings of
				 *		these Rexx return codes
				 */
				case EXEC_NOPARAM:	msg->rm_Result1 = 10; break;
				case EXEC_FAIL:		msg->rm_Result1 = 20; break;
				case EXEC_UNKNOWN:	msg->rm_Result1 = 30; break;
			}
		}
		ReplyMsg(msg);
	}
	InstallSettings(&newsettings, UpdateFlags);
}

/*
 *		SendRemote(cmdline, mode)
 *
 *		Sends a command to a remote copy of SnoopDos running in the
 *		background. The mode is MODE_CMDLINE or MODE_TOOLTYPE. The
 *		difference is that for MODE_TOOLYPE, HIDE commands are ignored.
 *
 *		In both cases, the command is first checked against the command
 *		table to make sure it's valid. If it's invalid, then if the mode
 *		is MODE_CMDLINE, an appropriate error message is printed.
 */
void SendRemote(char *cmdline, int mode)
{
	struct Command *cmd;
	struct MsgPort *ourport;
	char cmdname[100];
	char param[100];
	int boolvalue;
	int cid;

	cid = ParseCommand(cmdline, cmdname, param, &boolvalue, &cmd);
	switch (cid) {
		case CMD_UNKNOWN:
			if (mode == MODE_CMDLINE)
				Printf(MSG(MSG_ERROR_CLI_UNKNOWN), cmdline);
			return;

		case CMD_NOPARAM:
			if (mode == MODE_CMDLINE)
				Printf(MSG(MSG_ERROR_CLI_NOPARAM), cmdline);
			return;

		case CMD_HIDE:
			/*
			 *		Don't send a HIDE command to the current SnoopDos if
			 *		it was in an icon's tooltypes
			 */
			if (mode == MODE_TOOLTYPE)
				return;
			break;

		case CMD_END:
			return;
	}
	
	/*
	 *		Got a command that's okay to send. Now locate SnoopDos port
	 *		and send it. We have to locate the port each time, in case
	 *		the user quits the background copy suddenly.
	 */
	if (!RemoteReplyPort) {
		RemoteReplyPort = CreateMsgPort();
		if (!RemoteReplyPort)
			return;
	}
	Forbid();
	ourport = FindPort(PORT_NAME);
	if (ourport) {
		struct RexxMsg msg;

		msg.rm_Action  			 = RXCOMM;
		msg.rm_Args[0]			 = cmdline;
		msg.rm_Node.mn_ReplyPort = RemoteReplyPort;

		PutMsg(ourport, &msg);
		WaitPort(RemoteReplyPort);
		GetMsg(RemoteReplyPort);
		Permit();
		if (mode == MODE_CMDLINE && msg.rm_Result1 >= RC_ERROR)
			Printf(MSG(MSG_ERROR_CLI_FAILED), cmdline);
	} else
		Permit();
}

/*
 *		ShowCommands(file)
 *
 *		Prints a neatly formatted list of recognised commands to
 *		the specified output file.
 */
void ShowCommands(BPTR file)
{
	int i;
	int numrows = (NUM_CMDNAMES + 3) / 4;

	FPrintf(file, MSG(MSG_CLI_HELPBANNER));
	for (i = 0; i < numrows; i++) {
		int j;

		if (CheckSignal(SIGBREAKF_CTRL_C)) {
			FPrintf(file, "^C\n");
			break;
		}
		for (j = i; j < NUM_CMDNAMES; j += numrows) {
			char *pmsg = (CommandTable[j].numparms > 0) ? "*" : " ";
			char *cmsg = CommandTable[j].name;

			if (j < (NUM_CMDNAMES - numrows))
				FPrintf(file, "%s%-18s", pmsg, cmsg);
			else
				FPrintf(file, "%s%s", pmsg, cmsg);
		}
		FPrintf(file, "\n");
	}
}

/*
 *		ParseStartupOpts()
 *
 *		Handles the startup options, either on the command line or via
 *		the tooltypes, and sets the default options accordingly.
 *
 *		We handle a couple of options specially (LOCALE and SETTINGS) since
 *		these must be set before doing anything else at all (we can't even
 *		print a meaningful error message without LOCALE, for example.)
 *
 *		After those, we read in our configuration file from disk, and then
 *		parse the remaining items which can thus override any of the options
 *		specified in the config file. This applies even to tooltypes, so if
 *		an icon has a certain option hardcoded into it, this will always
 *		override a config file with the same option set.
 *
 *		Normally, CLI and Tooltype options are only used to control things
 *		like opening a default logfile, set the Commodity priority or config
 *		file name, making SnoopDos start in the background, or automatically
 *		opening one or more windows.
 *
 *		As a final step, if we spot that we are not the main instance of
 *		SnoopDos, we send all the new commands to the background copy instead.
 *		 We also signal the background copy to come to the foreground by
 *		default (Workbench) or if no other options are specified (CLI).
 *
 *		Returns TRUE for success, FALSE for failure.
 *
 */
int ParseStartupOpts(int argc, char **argv)
{
	struct Settings newset = DefaultSettings;
	struct DiskObject *IconCache[20];
	struct DiskObject *dobj = NULL;
	struct WBArg *wbarg;
	char msg[200];
	int success = 1;
	int maxargs;
	int i;
	int gotsettings = 0;		/* If true, settings keyword specified */

	/*
	 *		We start off in a special disabled state (not 0, not 1)
	 *		to ensure no events get monitored during our initialisation.
	 *		We use a special state so that we can detect if the user
	 *		specifies Disabled = Yes/No during startup.
	 */
	Disabled = 2;		/* Start up in disabled state */

	if (WBenchMsg) {
		/*
		 *		Starting up from Workbench.
		 */
		if (!IconBase)
			return (FALSE);

		maxargs = min(WBenchMsg->sm_NumArgs, 20);

		for (i = 0, wbarg = WBenchMsg->sm_ArgList; i < maxargs; i++, wbarg++) {
			BPTR olddir;

			IconCache[i] = NULL;
			if (wbarg->wa_Lock && *wbarg->wa_Name) {
				olddir = CurrentDir(wbarg->wa_Lock);
				dobj   = GetDiskObject(wbarg->wa_Name);
				if (dobj) {
					char *param;
					
					param = FindToolType(dobj->do_ToolTypes,
										 CmdNames[CMD_SETTINGS]);
					if (param && *param) {
						strcpy(DefaultConfigName, param);
						gotsettings = 1;
					}
					param = FindToolType(dobj->do_ToolTypes,
										 CmdNames[CMD_LANGUAGE]);
					if (param && *param)
						strcpy(Language, param);

					param = FindToolType(dobj->do_ToolTypes,
										 CmdNames[CMD_PATCHRAMLIB]);
					if (param && stricmp(param, "no") == 0)
						NoPatchRamLib = 1;

					IconCache[i] = dobj;
				}
				CurrentDir(olddir);
			}
		}
	} else {
		/*
		 *		Starting up from the CLI so scan the command line using
		 *		the passed-in arguments.
		 */
		if (argc > 1) {
			if (argv[1][0] == '-' || argv[1][0] == '?') {
				Printf(MSG(MSG_CLI_USAGE), CommodityTitle, argv[0]);
				return (FALSE);
			}
		}

		/*
		 *		Do a prescan of the command line looking
		 *		for SETTINGS, LANGUAGE or PATCHRAMLIB, keywords.
		 */
		for (i = 1; i < argc; i++) {
			char cmdname[50];
			char param[150];
			char newcmd[200];
			int boolvalue;
			struct Command *cmd;
			int cid;

			cid = ParseCommand(argv[i], cmdname, param, &boolvalue, &cmd);
			if (cid == CMD_NOPARAM && ((i + 1) < argc)) {
				/*
				 *		Indicates we didn't get enough parameters for our
				 *		command. Let's grab the next option off the command
				 *		line instead and see if that makes sense.
				 */
				mysprintf(newcmd, "%s %s", argv[i], argv[i+1]);
				cid = ParseCommand(newcmd, cmdname, param, &boolvalue, &cmd);
				i++;	/* Skip over next parameter */
			}
			switch (cid) {
				case CMD_SETTINGS:
					strcpy(DefaultConfigName, param);
					gotsettings = 1;
					break;

				case CMD_LANGUAGE:
					strcpy(Language, param);
					break;

				case CMD_PATCHRAMLIB:
					NoPatchRamLib = !boolvalue;
					break;
			}
		}
	}

	/*
	 *		Now initialise locale according to the language chosen,
	 *		and read in our default configuration file.
	 */
	InitLocale(Language);
	InitMenus();
	/*
	 *		Note that LoadConfig() and further ExecCommand() calls may
	 *		cause the settings to be installed at any time, and UpdateFlags
	 *		to be reset to null. Thus, it is important to ensure that
	 *		we initialise them correctly in the first place.
	 */
	UpdateFlags = SET_ALL;
	if (SnoopPort) {
		/*
		 *		We only initialise our patches if we're the only task
		 *		running at the moment. We leave the initialisation
		 *		until now so that we can control whether or not our
		 *		ramlib patch gets installed.
		 */
		if (!InitPatches()) {
			if (WBenchMsg)
				ShowError(MSG(MSG_ERROR_INITPATCHES));
			else
				Printf("%s\n", MSG(MSG_ERROR_INITPATCHES));
			Cleanup(40);
		}
		if (!gotsettings) {
			/*
			 *		If we haven't yet got a settings file, then try a number of
			 *		possible locations (in order of priority). If we find a
			 *		match, we overwrite the default config name with the new
			 *		name, for future use by LoadConfig and SaveConfig.
			 */
			static char *defconfig[] = {
				"PROGDIR:" SETTINGS_BASENAME,
				"S:"       SETTINGS_BASENAME,
				"ENVARC:"  SETTINGS_BASENAME,
				NULL
			};
			APTR oldwinptr = *TaskWindowPtr;
			char **pdefname;

			*TaskWindowPtr = (APTR)-1;
			for (pdefname = defconfig; *pdefname; pdefname++) {
				BPTR lk = Lock(*pdefname, ACCESS_READ);

				if (lk) {
					UnLock(lk);
					break;
				}
			}
			if (!*pdefname) {
				/*
				 *		Choose a good default name. We try for ENVARC:
				 *		initially, but if that fails, fall back on S:
				 */
				if (IsFileSystem(defconfig[2]))
					pdefname = &defconfig[2];
				else
					pdefname = &defconfig[1];
			}
			strcpy(DefaultConfigName, *pdefname);
			strcpy(ConfigFileName,	  *pdefname);
			*TaskWindowPtr = oldwinptr;
		} else {
			/*
			 *		Do a quick check to see if the filename specified
			 *		using the Settings keyword exists -- if it doesn't,
			 *		then don't bother to try loading it (this avoids
			 *		a requester being shown unnecessarily).
			 */
			BPTR lk = Lock(DefaultConfigName, ACCESS_READ);

			if (lk)
				UnLock(lk);
			else
				gotsettings = 0;
		}

		/*
		 *		Now, one way or another, we have the default config filename
		 *		set up so try and load it.
		 */
		if (!LoadConfig(DefaultConfigName,
						(WBenchMsg ? MODE_TOOLTYPE : MODE_CMDLINE), &newset)
			&& gotsettings)
		{
			ShowError(MSG(MSG_ERROR_LOADING_SETTINGS), DefaultConfigName);
		}
	}

	/*
	 *		Finally, lets parse the remaining startup options and/or icons
	 */
	if (WBenchMsg) {
		/*
		 *		Scan all the config files (if any) that were passed as
		 *		parameters via icons.
		 */
		wbarg = WBenchMsg->sm_ArgList + 1;
		for (i = 1; i < WBenchMsg->sm_NumArgs; i++, wbarg++) {
			char *name = wbarg->wa_Name;

			if (SnoopPort) {
				BPTR olddir = CurrentDir(wbarg->wa_Lock);

				LoadConfig(name, MODE_INTERNAL, &newset);
				CurrentDir(olddir);
			} else {
				/*
				 *		There's a background copy running so we need
				 *		to tell it to load the config file instead.
				 */
				char *p;

				mysprintf(msg, "%s ", CmdNames[CMD_LOADSETTINGS]);
				p = msg + strlen(msg);
				NameFromLock(wbarg->wa_Lock, p, 150);
				AddPart(p, wbarg->wa_Name, 150);
				SendRemote(msg, MODE_TOOLTYPE);
			}
		}

		/*
		 *		Rescan the icons, this time processing all the tooltypes that
		 *		we recognise.  Any we don't recognise are ignored. SETTINGS
		 *		command may get parsed a second time, but that's okay.
		 */
		for (i = 0; i < maxargs; i++) {
			dobj = IconCache[i];
			if (dobj) {
				char **tooltypes;

				for (tooltypes = dobj->do_ToolTypes; *tooltypes; tooltypes++) {
					if (SnoopPort)
						ExecCommand(*tooltypes, MODE_TOOLTYPE, &newset);
					else
						SendRemote(*tooltypes, MODE_TOOLTYPE);
				}
				FreeDiskObject(dobj);
			}
		}
		if (!SnoopPort)
			SendRemote(CmdNames[CMD_SHOW], MODE_CMDLINE);
	} else {
		/*
		 *		Do a proper scan of the CLI arguments.
		 */
		for (i = 1; i < argc; i++) {
			char newcmd[200];
			char *thiscmd = argv[i];
			struct Command *cmd;
			int boolvalue;
			int cid;

			/*
			 *		Check if the command we're checking needs more than
			 *		one parameter. If so, and if it wasn't supplied, we
			 *		assume that the parameter after it on the command
			 *		line is to be used. This means the user doesn't have
			 *		to specifically type the '='.
			 *
			 *		HELP is a special case, since HELP will work with zero
			 *		parameters too. We only try and handle help if there
			 *		is at least one more parameter after it on the command
			 *		line. If there isn't, then we print a list of supported
			 *		commands instead.
			 */
			cid = ParseCommand(thiscmd, newcmd, newcmd+50, &boolvalue, &cmd);
			if ( (i+1) < argc && (cid == CMD_HELP ||
								  (cid == CMD_NOPARAM && cmd->numparms > 0)) )
			{
				/*
				 *		We didn't have a parameter for this command, so
				 *		assume the next parameter on the command line is
				 *		the parameter instead.
				 */
				mysprintf(newcmd, "%s %s", argv[i], argv[i+1]);
				thiscmd = newcmd;
				i++;
			} else if (cid == CMD_HELP) {
				/*
				 *		Didn't have anything following the HELP so
				 *		print CLI help instead and exit immediately.
				 */
				ShowCommands(Output());
				return (0);
			}
			if (SnoopPort) {
				switch (ExecCommand(thiscmd, MODE_CMDLINE, &newset)) {
					case EXEC_FAIL:
						Printf(MSG(MSG_ERROR_CLI_FAILED), thiscmd);
						success = 0;
						break;

					case EXEC_NOPARAM:
						Printf(MSG(MSG_ERROR_CLI_NOPARAM), thiscmd);
						success = 0;
						break;

					case EXEC_UNKNOWN:
						Printf(MSG(MSG_ERROR_CLI_UNKNOWN), thiscmd);
						success = 0;
						break;

					// case EXEC_OKAY:
				}
			} else {
				SendRemote(thiscmd, MODE_CMDLINE);
			}
		}
		if (!SnoopPort && argc == 1)
			SendRemote(CmdNames[CMD_SHOW], MODE_CMDLINE);
	}
	if (!SnoopPort)
		success = 0;

	/*
	 *		Now check if the user specified Disable=YES (or NO) during
	 *		startup. If they didn't, then reset the disable flag.
	 *		Regardless, we want to update the function flags to reflect
	 *		the current disable state.
	 */
	if (Disabled == 2)
		SetMonitorMode(MONITOR_NORMAL);

	if (success)
		InstallSettings(&newset, UpdateFlags | SET_FUNC);

	return (success);
}
