/* Y o u r   D e s c r i p t i o n                       */
/*                            AppBuilder Photon Code Lib */
/*                                         Version 0.91B */

/* Standard headers */
#include <time.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <process.h>
#include <sys/wait.h>
#if defined(__QNXNTO__)
	#include <sched.h>
	#include <sys/dcmd_chr.h>
#else
	#include <sys/dev.h>
	#include <sys/sched.h>
	#include <sys/qnx_glob.h>
#endif

#include <assert.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>
#include <photon/PhGulliver.h>

/* Local headers */
#include "phterm.h"
#include "abimport.h"
#include "proto.h"

#define IDSTR "Photon Terminal Configuration File\n"
#define OLDVER	90	/* The oldest version number */
#define VERSION	92  /* The current version number */

static struct Hdr {
	const char id[ sizeof(IDSTR) ];
	unsigned short ver;
	}
		cfgid = { IDSTR, VERSION };

unsigned long xflags = 0;


/* Mask for putting ttyflags into Pt_ARG_TTY_FLAGS */
#define TTYFLAGS ( Pt_TTY_SETENV | Pt_TTY_ARGV0 )

static int ttyflags = TTYFLAGS;


struct CfgHdr cfg = {
	{ 25, 80 },
	{  1,  1 },
	{ Pt_TERM_MAX_ROWS, Pt_TERM_MAX_COLS },
	0, -1, 0
	};

struct XCfgHdr xcfg = { 0, 64 };

char
	devopt='R', *csfile,
	*device, *devfd,
	**shargs,
	*title, *ptermrc, *cmdrc;

unsigned short nfontsets;
struct FontSet *fontsets, *curfontset;

PgColor_t palette[ PALETTE_SIZE ];

static char *cfgpath;

static FILE *fp;

static char *dfltcustfont, *cmdlinefont, **cfgfs, *cfgcsf;

/**************  Config file write errors  *************/

static int writeerr;

static void cfwerr( void ) {
	int modal, blocked;
	PtWidget_t *config;
	
	writeerr = errno;

	ApCreateModule( ABM_saveerr, ABW_but_configSave, NULL );

	blocked = getres( config = ABW_config, Pt_ARG_FLAGS );
	setres_len( ABW_config, Pt_ARG_FLAGS, Pt_BLOCKED, Pt_BLOCKED );

	modal = PtModalStart();
	do	PtProcessEvent();
		while ( writeerr != -1 );
	PtModalEnd( modal );
	setres_len( ABW_config, Pt_ARG_FLAGS, blocked, Pt_BLOCKED );
	}


int
sverrInit( PtWidget_t *link_instance, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )
	{
	char *buf;
	static char fmt[] = "Cannot write the file\n%s:\n%s";
	const char *msg = strerror( writeerr );
	if	(	(	buf
			=	malloc( strlen(cfgpath) + strlen(msg) + sizeof(fmt) )
				)
		==	NULL
			)
		return Pt_END;

	sprintf( buf, fmt, cfgpath, msg );
	setstring( ABW_saveerrMsg, buf );
	free( buf );
	/* eliminate 'unreferenced' warnings */
	link_instance = link_instance, apinfo = apinfo, cbinfo = cbinfo;

	return Pt_CONTINUE;
	}

int
sverrDestroy( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )
	{
	/* eliminate 'unreferenced' warnings */
	widget = widget, apinfo = apinfo, cbinfo = cbinfo;

	writeerr = -1;

	return Pt_CONTINUE;
	}


/***********  Some general functions **************/

static void nomem( void ) {
	#pragma aux nomem aborts
	fputs( "No memory\n", stderr );
	exit( EXIT_FAILURE );
	}

static void *gtmem( unsigned n ) {
	void *mem;
	if ( ( mem = malloc(n) ) == NULL )
		nomem();
	return mem;
	}

static char *dupstr( const char *str ) {
	return strcpy( gtmem( strlen(str) + 1 ), str );
	}

static char *dupopt( void ) {
	return dupstr( optarg );
	}

static void optdup( char **varp ) {
	if ( *varp )
		free( *varp );
	*varp = dupopt();
	}

static void badcfg( void ) {
	#pragma aux badcfg aborts
	fprintf( stderr, "Bad config file %s\n", cfgpath );
	exit( EXIT_FAILURE );
	}


static void cfclose( void ) {
	if ( fp ) {
		fclose( fp );
		fp = NULL;
	}	}

static int cftryget( void *buf, unsigned n ) {
	int r;
	if	(	( r = fread( buf, 1, n, fp ) ) != n
		&&	r != 0
			)
		badcfg();
	return r;
	}

static void cfget( void *buf, unsigned n ) {
	if ( fread( buf, 1, n, fp ) != n )
		badcfg();
	}

static char *cftrygets( void ) {
	unsigned short len;
	if ( ! cftryget( &len, sizeof(len) ) )
		return NULL;
	else {
		char *buf;
		PhEndianFixInt( &len );
		buf = gtmem( len + 1 );
		buf[ len ] = '\0';
		cfget( buf, len );
		return buf;
	}	}

static char *cfgets( void ) {
	char *result;
	if ( ( result = cftrygets() ) == NULL )
		badcfg();
	return result;
	}

static void cfput( void const *buf, unsigned n ) {
	if	(	fp != NULL
		&&	fwrite( buf, 1, n, fp ) != n
			) {
		cfwerr();
		cfclose();
	}	}

static void cfputs( const char *str ) {
	unsigned short len = strlen(str);
	PhEndianFixInt( &len );
	cfput( &len, sizeof(len) );
	cfput( str, len );
	}

const char *afterslash( const char *path ) {
	#pragma aux afterslash modify nomemory
	const char *slash;
	return
		( slash = strrchr( path, '/' ) ) != NULL
		?	slash + 1
		:	path;
	}

static char *mkcfgpath(
		const char *env, const char *dflt,
		const char *subdir, const char *prog,
		const char *app, const char *ext
		) {
	/* Build config file path */
	const char *s; unsigned len;
	if	(	( s = getenv( env ) ) == NULL
		||	s[0] == '\0'
			)
		s = dflt;
	else
		if ( s[0] == '/' && s[1] == '\0' )
			++s; /* Make it "" to avoid "//" at the beginning */
	prog = afterslash( prog );
	len = strlen(s) + strlen(prog) + strlen(subdir) + strlen(prog) + strlen(ext) + 4;
	if ( app != NULL )
		len += strlen( app = afterslash( app ) ) + 1;
	cfgpath = gtmem( len );
	if ( app )
		sprintf( cfgpath, "%s/%s/%s.%s.%s", s, subdir, prog, app, ext );
	else
		sprintf( cfgpath, "%s/%s/%s.%s", s, subdir, prog, ext );
	return cfgpath;
	}

#if !defined(__BIGENDIAN__)
	#define cfgfix() ((void)0)
	#define xcfgfix() ((void)0)
#else
	static void rcfix( PtTerminalRowCol_t *rc ) {
		PhEndianFixInt( &rc->r );
		PhEndianFixInt( &rc->c );
		}

	static void cfgfix( void ) {
		rcfix( &cfg.size );
		rcfix( &cfg.mins );
		rcfix( &cfg.maxs );
		PhEndianFixInt( &cfg.ncustfonts );
		PhEndianFixInt( &cfg.fidx );
		PhEndianFixInt( &cfg.flags );
		}

	static void xcfgfix( void ) {
		PhEndianFixInt( &xcfg.fontset );
		PhEndianFixInt( &xcfg.scrlbk );
		}
#endif /* defined(__BIGENDIAN__) */

static int cfgread( char *path ) {
	if ( ( fp = fopen( cfgpath = path, "r" ) ) == NULL )
		return 0;
	else {
		int i;
		struct Hdr id;
		cfget( & id, sizeof(id) );
		if ( memcmp( &id.id, &cfgid.id, sizeof(cfgid.id) ) )
			badcfg();
		cfget( &cfg, sizeof(cfg) );
		cfgfix();
		PhEndianFixInt( &id.ver );
		if ( id.ver < OLDVER || id.ver > VERSION )
			badcfg();
		if ( id.ver >= 91 ) {
			cfget( &xcfg, sizeof(xcfg) );
			xcfgfix();
			}

		dfltcustfont = cfgets();
		title = cfgets();
		if ( cfg.ncustfonts != 0 )
			if ( xflags & XF_NFLST ) {
				for ( i=0; i<cfg.ncustfonts; ++i )
					free( cfgets() );
				cfg.ncustfonts = 0;
				}
			else {
				cfgfs = gtmem( cfg.ncustfonts * sizeof(*cfgfs) );
				for ( i=0; i<cfg.ncustfonts; ++i )
					cfgfs[i] = cfgets();
				if ( id.ver <= 91 )
					/* Remove the 'f' flag if it's the last character */
					for ( i=0; i<cfg.ncustfonts; ++i ) {
						char *n, *f;
						if ( ( f = strrchr( n = cfgfs[i], 'f' ) ) != NULL ) {
							while	(	n != f
									&&	! isdigit( *n )
										)
								++n;
							if ( f != n )
								while ( ( f[0] = f[1] ) != '\0' )
									++f;
				}		}	}
		cfgcsf = cftrygets();
		cfclose();
		return 1;
	}	}

static const char
	HOME[] = "HOME",
	PHOTON_PATH[] = "PHOTON_PATH",
	CONFIG[] = "config",
	DOTPHOTON[] = ".photon",
	USRPHOTON[] = "/usr/photon",
	PTERM[] = "pterm",
	DOT[] = ".";

static void cfgfind( const char *prog ) {
	static const char RC[] = "rc";
	if	(	cmdrc == NULL
		||	cmdrc[0] == '\0'
			) {
		/* There was no -C option or the path was empty */
		const char *cmd;
		if	(	shargs == NULL
			||	( cmd = shargs[0] ) == NULL
				)
			cmd = PtTtyShell();
		cmdrc = mkcfgpath( HOME, DOT, DOTPHOTON, prog, cmd, RC );
		}
	if	(	( ptermrc = getenv( "PTERMRC" ) ) == NULL
		||	ptermrc[0] == '\0'
			)
		ptermrc = mkcfgpath( HOME, DOT, DOTPHOTON, prog, NULL, RC );
	if ( !( xflags & XF_NCFGF ) )
		if ( cmdrc && cfgread( cmdrc ) )
			xflags |= XF_SPECF;
		else
			if ( ! cfgread( ptermrc ) ) {
				cfgread( mkcfgpath( PHOTON_PATH, USRPHOTON, CONFIG, prog, NULL, RC ) );
				free( cfgpath );
	}			}

static void readfontsets( void ) {
	ApDBase_t *db; struct FontSet *fs;
	short dfltset, dfltcust;
	PtWidget_t *wgt; PtArg_t arg[3]; int i;
	dfltset = dfltcust = 0;
	if ( ( db = ApOpenDBase( ABM_config ) ) != NULL ) {
		wgt = ApCreateWidget( db, "combo_fontSet", 0, 0, 0, NULL );
		ApCloseDBase( db );
		if ( wgt != 0 ) {
			PtSetArg( &arg[0], Pt_ARG_ITEMS, 0, 0 );
			PtSetArg( &arg[1], Pt_ARG_CBOX_SELECTION_ITEM, 0, 0 );
			PtGetResources( wgt, 2, arg );
			PtDestroyWidget( wgt );
			nfontsets = arg[0].len;
			dfltset   = arg[1].value;
		}	}
	if ( nfontsets <= 0 )
		nfontsets = 1;
	fontsets = fs = gtmem( nfontsets * sizeof(*fontsets) );
	for	(	dfltcust = cfg.ncustfonts;
			dfltcust > 0 && strcmp( dfltcustfont, cfgfs[ --dfltcust ] );
			);

	db = ApOpenDBase( ABM_fontlists );
	for ( i=0; i<nfontsets; ++fs ) {
		char wn[ 20 ];
		fs->saved  = NULL;
		fs->csfile = NULL;
		fs->csdata = NULL;
		fs->csets  = NULL;
		sprintf( wn, "fontset%d", ++i );
		if	(	db == NULL
			||	( wgt = ApCreateWidget( db, wn, 0, 0, 0, NULL ) ) == NULL
				) {
			fs->dispnames = fs->fullnames = cfgfs;
			fs->nfonts = cfg.ncustfonts;
			fs->dflt = dfltcust;
			fs->csfile = cfgcsf;
			fs->prefix = NULL;
			cfg.ncustfonts = 0;
			dfltcust = NULL;
			cfgfs = NULL;
			cfgcsf = NULL;
			}
		else {
			char *prfx = NULL, **pp, **qq;
			unsigned pl;
			PtSetArg( &arg[0], Pt_ARG_ITEMS, 0, 0 );
			PtSetArg( &arg[1], Pt_ARG_USER_DATA, &prfx, 0 );
			PtSetArg( &arg[2], Pt_ARG_CBOX_SELECTION_ITEM, 0, 0 );
			PtGetResources( wgt, 3, arg );
			pl = strlen(prfx);
			fs->prefix = dupstr( prfx );
			fs->dispnames = pp = gtmem( sizeof(char*) * 2 * arg[0].len );
			fs->nfonts = arg[0].len;
			fs->dflt = arg[2].value <= 0 ? 0 : arg[2].value - 1;
			memcpy( pp, (void*) arg[0].value, sizeof(char*) * arg[0].len );
			fs->fullnames = qq = pp + arg[0].len;
			assert( arg[0].len > 0 && prfx[0] != '\0' );
			do	{
				char *p = *pp;
				p = *qq++ = strcat(
					strcpy( gtmem( pl + strlen(p) + 1 ), prfx ), p
					);
				*pp++ = p + pl;
				}	while ( --arg[0].len );
			PtDestroyWidget( wgt );
		}	}
	if ( db )
		ApCloseDBase( db );
	while	(	xcfg.fontset > nfontsets
			||	xcfg.fontset <= 0
				) {
		xcfg.fontset = dfltset;
		dfltset = 1; /* One is OK */
		}
	curfontset = fontsets + xcfg.fontset - 1;
	if ( cfg.fidx >= curfontset->nfonts )
		cfg.fidx = -1;
	else
		if ( cfg.fidx >= 0 )
			curfontset->dflt = cfg.fidx;
	if	(	cfg.fidx == -1
		&&	cmdlinefont != NULL
			) {
		int j;
		for	(	i = curfontset->nfonts;
				--i >= 0 && strcmp( cmdlinefont, curfontset->fullnames[i] );
				);
		if ( i < 0 ) {
			fs = fontsets;
			for ( j = nfontsets; i < 0 && --j; ++fs ) {
				for	(	i = fs->nfonts;
						--i >= 0 && strcmp( cmdlinefont, fs->fullnames[i] );
						);
				if ( i >= 0 )
					xcfg.fontset = ( curfontset = fs ) - fontsets + 1;
			}	}
		if ( i >= 0 )
			curfontset->dflt = cfg.fidx = i;
	}	}

int cfgsave( void ) {
	/* Save config file */
	cfgpath = xflags & XF_SPECF ? cmdrc : ptermrc;
	if ( cfgpath != NULL )
		if ( ( fp = fopen( cfgpath, "w" ) ) == NULL ) {
			cfwerr();
			return -1;
			}
		else {
			int i;
			struct FontSet *fs = fontsets;
			for ( i = nfontsets; --i >= 0 && fs->prefix; ++fs );
			cfg.ncustfonts = i < 0 ? 0 : fs->nfonts;
			cfput( &cfgid, sizeof(cfgid) );
			cfgfix(); xcfgfix();
			cfput( &cfg, sizeof(cfg) );
			cfput( &xcfg, sizeof(xcfg) );
			cfgfix(); xcfgfix();
			cfputs( curfontset->fullnames[ curfontset->dflt ] );
			cfputs( title );
			for ( i=0; i<cfg.ncustfonts; ++i )
				cfputs( fs->fullnames[i] );
			if ( curfontset->csfile )
				cfputs( curfontset->csfile );
			if ( fp )
				cfclose();
			else
				return -1;
			}
	return 0;
	}

/***********  Palette file  **********/

#define PAL_LINE 9

static void palcvt( const char *txt ) {
	PgColor_t *dst = palette;
	int cnt = PALETTE_SIZE;
	unsigned dig, pos=0;
	PgColor_t col=0;
	for (;;)
		if ( ++pos == PAL_LINE )
			if ( *txt++ == '\n' ) {
				*dst++ = col;
				pos = col = 0;
				if ( --cnt == 0 ) {
					xflags |= XF_PAL;
					return;
				}	}
			else
				return;
		else {
			if ( isdigit( dig = *txt++ ) )
				dig -= '0';
			else
				if ( isxdigit( dig ) )
					dig -= isupper(dig) ? 'A'-10 : 'a'-10;
				else
					return;
			col = col << 4 | dig;
	}		}

static void getpal( void ) {
	struct {
		union {
			PgColor_t bin[ PALETTE_SIZE ];
			char txt[ PALETTE_SIZE * PAL_LINE ];
			}
				t;
		char c;
		}
			buf;
	if ( ( fp = fopen( cfgpath, "r" ) ) != NULL ) {
		switch ( fread( &buf, 1, sizeof(buf), fp ) ) {
			case sizeof(buf.t.bin) :
				memcpy( palette, buf.t.bin, sizeof(palette) );
				xflags |= XF_PAL;
				break;
			case sizeof(buf.t.txt) :
				palcvt( buf.t.txt );
				break;
			}
		if ( !( xflags & XF_PAL ) )
			fprintf( stderr, "Bad palette file %s\n", cfgpath );
		fclose( fp );
	}	}

static void loadpalette( void ) {
	char *save = cfgpath;
	if ( ( cfgpath = getenv( "PTERMPAL" ) ) != NULL )
		getpal();
	else {
		static const char EXT[] = "pal";
		mkcfgpath( PHOTON_PATH, USRPHOTON, CONFIG, PTERM, NULL, EXT );
		getpal();
		free( cfgpath );
		if ( ! ( xflags & XF_PAL ) ) {
			mkcfgpath( HOME, DOT, DOTPHOTON, PTERM, NULL, EXT );
			getpal();
			free( cfgpath );
		}	}
	cfgpath = save;
	}

/***********  Cmd line arguments parsing  **********/

static void addfont( void ) {
	int const i = cfg.ncustfonts;
	if ( ( cfgfs = realloc( cfgfs, sizeof(cfgfs[0]) * ++cfg.ncustfonts ) ) == NULL )
		nomem();
	cfgfs[ i ] = dupopt();
	}

static void getsize( PtTerminalRowCol_t *size ) {
	char *ptr;
	size->r = strtoul( optarg, &ptr, 10 );
	if ( *ptr == 'x' )
		size->c = strtoul( ptr+1, NULL, 10 );
	}

struct BitOptions {
	unsigned short bit, mask;
	char opt;
	#if defined(__QNXNTO__)
		char padding;
	#endif
	};

static const struct BitOptions *bit_opt( char c ) {
	static const struct BitOptions tab[] = {
		{ FL_ALT, FL_ALT,          'A' },
		{ FL_CNB, FL_CNB | FL_CAB, 'b' },
		{ FL_CAB, FL_CNB | FL_CAB, 'B' },
		{ FL_QNX, FL_QNX,          'Q' },
		{ FL_MSE, FL_MSE,          'T' },
		{ FL_VBL, FL_VBL,          'v' },
		};
	const struct BitOptions *p;
	for ( p = tab; p->opt != c; )
		if ( ++p == tab + sizeof(tab) / sizeof(tab[0]) )
			return NULL;
	return p;
	}

static PtTtySpawnOptions_t spawn_options;

const char ApOptions[]
=	AB_OPTIONS "a:d:C:D:f:F:g:H:u:m:M:N:R:t:AbcBeEGnlLpPqQTXvzZ";

#if defined(__QNXNTO__)
	#define VALUE_TO_RESET_optind 1
#else
	#define VALUE_TO_RESET_optind 0
#endif

int init( int argc, char *argv[] ) {
	int opt, bitmask = ~0u;
	const struct BitOptions *bop;
	spawn_options = PtTtyDefaultSpawnOptions;
	while ( ( opt = getopt( argc, argv, ApOptions ) ) != -1 )
		switch ( opt ) {
			case '?' : exit( EXIT_FAILURE );
			case 'q' : xflags |= XF_EXIT; break;
			case 'n' : xflags |= XF_NCFGF; break;
			case 'C' : xflags |= XF_DASHC; cmdrc = optarg; break;
			case 'R' : /* nid   Use a pty on that node */
			case 'd' : /* path  Open given device instead of using a ptty */
			case 'D' : /* fd    Attach given file descriptor instead of using a ptty */
				devopt = opt;
				device = optarg;
				break;
			case 'N' : /* fd    Output device name to this fd */
				devfd = optarg;
				break;
			case 'u' :
				while ( ( opt = *optarg++ ) != '\0' )
					if ( ( bop = bit_opt( opt ) ) != NULL )
						bitmask &= ~bop->mask;
					else
						if ( opt == 'a' )
							xflags |= XF_NFLST;
						else {
							fprintf( stderr,
								"Invalid option in the -u option ('%c')\n",
								opt
								);
							exit( EXIT_FAILURE );
			}				}
	if ( optind < argc ) {
		ttyflags |= Pt_TTY_ARGV0;
		shargs = &argv[ optind ];
		xflags |= XF_CMD;
		}
	else
		if	(	devopt == 'R'
			&&	devfd == NULL
				) {
			static char *empty[1] = { NULL };
			shargs = empty;
			}
	#if !defined(__QNXNTO__)
	/* Start Dev.pty if /dev/ptyp0 doesn't exist */
	if	(	devopt == 'R'
		&&	device == NULL
			) {
		static const char devpty[] = "//0/dev/ptyp0";
		if	(	access( devpty, F_OK ) == -1
			&&	errno == ENOENT
				) {
			static const char homepty[] = "/dev/ptyp0";
			if ( access( homepty, F_OK ) == 0 )
				device = "/dev";
			else {
				static const char
					Dev_pty[] = "/bin/Dev.pty",
					*const args[] = { Dev_pty, NULL };
				fputs( "pterm: starting Dev.pty\n", stderr );
				if	(	qnx_spawn( 0, NULL, 0, -1, -1,
							_SPAWN_NOZOMBIE | _SPAWN_BGROUND | _SPAWN_NOHUP | _SPAWN_NEWPGRP,
							Dev_pty, args, environ, NULL, 0
							)
					<	0
						)
					perror( Dev_pty );
				else {
					int count = 30;
					while	(	access( devpty, F_OK ) == -1
							&&	errno == ENOENT
							&&	--count
								)
						delay( 100 );
		}	}	}	}
	#endif
	cfgfind( argv[ 0 ] );
	cfg.flags &= bitmask;
	optind = VALUE_TO_RESET_optind;
	while ( ( opt = getopt( argc, argv, ApOptions ) ) != -1 )
		switch ( opt ) {
			case '?' :
				exit( EXIT_FAILURE );
			case 'a' : /* font  Add the font to the font list */
				addfont();
				break;
			case 'A' : /*       Pass all Alt key combinations to the device */
//			case '8' : /*       Use the 8859-1 character set instead of PC set */
			case 'b' : /*       Cursor never blinks */
			case 'B' : /*       Cursor always blinks */
			case 'Q' : /*       Use QNX protocol */
			case 'T' : /*       Disable text-mode mouse */
			case 'v' : /*       Visual bell */
				bop = bit_opt( opt );
				cfg.flags = cfg.flags & ~bop->mask | bop->bit;
				break;
			case 'c' : /*       Disable config dialog */
				xflags |= XF_NOCFG;
				ApModifyItemState( &menu, AB_ITEM_DIM, ABN_properties, NULL );
				break;
			case 'e' : /*       Ignored for compatibility with old Pterm */
				break;
			case 'E' : /*       Don't set the TERM environment variable */
				ttyflags &= ~Pt_TTY_SETENV;
				break;
			case 'f' : /* font  Use the given font name as the current font */
				cmdlinefont = optarg;
				cfg.fidx = -1;
				break;
			case 'F' : /* n     Use the n-th font from list */
				cfg.fidx = atoi(optarg);
				break;
			case 'g' : /* RxC   Set size to R rows, C columns */
				getsize( &cfg.size );
				break;
			case 'H' : /* N      Save up to N scrolled-back lines */
				xcfg.scrlbk = atoi(optarg);
				break;
			case 'l' : /*       Start shell as a login shell */
				ttyflags &= ~Pt_TTY_ARGV0;
				break;
			case 'L' : /*       Don't create new sessions */
				#if defined(__QNXNTO__)
					spawn_options.options.flags
					=	spawn_options.options.flags
					&	~( SPAWN_SETSID | SPAWN_TCSETPGROUP )
					|	SPAWN_SETGROUP;
					spawn_options.options.pgroup = SPAWN_NEWPGROUP;
				#else
					spawn_options.flags &= ~_SPAWN_SETSID;
				#endif
				break;
			case 'm' : /* RxC   Set minimal size to R rows, C columns */
				getsize( &cfg.mins );
				break;
			case 'M' : /* RxC   Set maximal size to R rows, C columns */
				getsize( &cfg.maxs );
				break;
			case 'p' : /*       Decrease priority */
				xflags = xflags & ~XF_PRIUP | XF_PRIDN;
				break;
			case 'P' : /*       Increase program's priority */
				xflags = xflags & ~XF_PRIDN | XF_PRIUP;
				break;
			case 'G' : /*       Pass signals to the program */
				xflags |= XF_SIG;
				break;
			case 't' : /* title Set this window title */
				optdup( &title );
				break;
			case 'X' : /*       Exit with shell's exit status */
				xflags |= XF_XST;
				break;
			case 'z' : /*       Wait until closed explicitly */
				xflags |= XF_NXIT;
				break;
			case 'Z' : /*       If not 0, wait until closed explicitly */
				xflags |= XF_NXIT | XF_NX0;
				break;
			}
	loadpalette();
	optind = VALUE_TO_RESET_optind; /* Maybe someone will scan options again... */
	return Pt_CONTINUE;
	}

/*************  Signals  ***********/

static int in_foreground( void ) {
	/*	Return TRUE if we know that we're not started in background
	 *	(we assume that if both SIGINT and SIGQUIT are default and not masked).
	 *	In this case we won't detach from our process group.
	 */
	struct sigaction sa;
	sigaction( SIGINT, NULL, &sa );
	if ( sa.sa_handler != SIG_DFL )
		return 0;
	sigaction( SIGQUIT, NULL, &sa );
	if ( sa.sa_handler != SIG_DFL )
		return 0;
	sigprocmask( 0, NULL, &sa.sa_mask );
	return
		! sigismember( &sa.sa_mask, SIGINT )
	&&	! sigismember( &sa.sa_mask, SIGQUIT );
	}

static void attempt_suicide( int sig ) {
	/* Set to SIG_DFL and raise */
	signal( sig, SIG_DFL );
	raise( sig );
	/*	Usually we'll not survive the signal
	 *	-- unless it is ignored by default, like SIGWINCH
	 */
	}

static int sendsig( int sig ) {
	pid_t pid;
	return
		( pid = getres( ABW_tty, Pt_ARG_TTY_PID ) ) != 0
		&&	kill( pid, sig ) == 0;
	}

static void kill_shell( void ) {
	if ( sendsig( SIGHUP ) ) {
		delay( 50 ); /* Give SIGCHLD a chance */
		PtBkgdHandlerProcess();
		if ( sendsig( SIGTERM ) ) {
			delay( 50 ); /* Give SIGCHLD another chance */
			PtBkgdHandlerProcess();
	}	}	}

static int xstatus = EXIT_FAILURE;
static int xsig = 0;
static sigset_t lethal;

void finish( void ) {
	/* Terminate with the proper exit status or signal */
	if ( ( xflags & XF_XST ) == 0 )
		exit( EXIT_SUCCESS );
	else {
		if ( xsig )
			attempt_suicide( xsig );
		exit( xstatus );
	}	}

static PtSignalProcF_t sigcatcher;
static int sigcatcher( int sig, void *data ) {
	switch ( sig ) {
		default :
			if ( xflags & XF_SIG )
				sendsig( sig );
			else
				if ( sigismember( &lethal, sig ) ) {
		case SIGTERM :
					kill_shell();
					attempt_suicide( sig );
		}			}
	return Pt_CONTINUE;
	}

static void setupsignals( void ) {
	static const int nonlethal[] = {
		SIGCHLD,	SIGWINCH,	SIGURG,		SIGIO,
		SIGTSTP,	SIGCONT,	SIGTTIN,	SIGTTOU
		};
	sigset_t catched; int s;
	sigfillset( &lethal );
	for ( s=0; s<sizeof(nonlethal)/sizeof(nonlethal[0]); ++s )
		sigdelset( &lethal, nonlethal[ s ] );
	catched = lethal;
	#if ! CATCH_SEGV
		sigdelset( &catched, SIGSEGV );
		sigdelset( &catched, SIGILL );
		#endif
	PtAppAddSignalProc( NULL, &catched, sigcatcher, NULL );
	sigemptyset( &catched );
	sigaddset( &catched, SIGCHLD );
	sigprocmask( SIG_UNBLOCK, &catched, NULL );
	}

/************  Child termination  *******************/

int
terminated( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) {
	int status = * (int*) cbinfo->cbdata;
	if ( WIFSIGNALED( status ) )
		xsig = WTERMSIG( status );
	else
		if ( WIFEXITED( status ) )
			xstatus = WEXITSTATUS( status );
	if	(	!( xflags & XF_NXIT )
		||	(	( xflags & XF_NX0 )
			&&	WIFEXITED( status )
			&&	WEXITSTATUS( status ) == EXIT_SUCCESS
			)	)
		finish();

	/* eliminate 'unreferenced' warnings */
	widget = widget, apinfo = apinfo, cbinfo = cbinfo;

	return Pt_CONTINUE;
	}

/************  The title  *******************/

static char *titdst;
static unsigned titlen;

static void addchr( char c ) {
	if ( titdst )
		*titdst++ = c;
	else
		++titlen;
	}

static void addstr( const char *str ) {
	char c;
	if ( str != NULL )
		while ( ( c = *str++ ) != '\0' )
			addchr( c );
	}

static void addnum( long num ) {
	char buf[ 20 ];
	sprintf( buf, "%ld", num );
	addstr( buf );
	}

#if defined(__QNXNTO__)
	typedef struct _ttyinfo DEV_INFO;

	#define DEV_NAME( di )		( (di).ttyname )
	#define DEV_NID( di )		0
	#define DEV_SPID( di )		0
	#define DEV_PGRP( di )		0
	#define DEV_TTY( di )		0
	#define get_dev_info( fd, buf ) \
		devctl( fd, DCMD_CHR_TTYINFO, buf, sizeof(*(buf)), 0 )

#else
	typedef struct _dev_info_entry	DEV_INFO;
	#define DEV_NAME( di )		( (di).tty_name )
	#define DEV_NID( di )		( (di).nid )
	#define DEV_SPID( di )		( (di).session )
	#define DEV_PGRP( di )		( (di).pgrp )
	#define DEV_TTY( di )		( (di).tty )
	#define get_dev_info( fd, buf ) dev_info( fd, buf )
#endif

void set_title( void ) {
	DEV_INFO di;
	char prgpath[ CMDPATH_LEN + 1 ], *prgname;
	char prgargs[ TITLE_LEN + 2 + 1 ];
	char prgarg0[ TITLE_LEN + 2 + 1 ];
	char *devname, *devpath;
	char *str, c, *clk;
	const char *t = title;
	int fd = getttyres( Pt_ARG_TTY_FD );
	pid_t prg = getttyres( Pt_ARG_TTY_PID );
	if ( title == NULL )
		return;
	titlen = 0; titdst = NULL;
	if	(	xflags & XF_TDEV
		||	xflags & XF_TPRG && prg == 0
			) {
		memset( &di, 0, sizeof(di) );

		devpath = DEV_NAME( di );
		if ( fd == -1 )
			devname = strcpy( DEV_NAME( di ), "(closed)" );
		else
			if	(	get_dev_info( fd, &di ) == 0
				||	(	( devpath = getttyptr( Pt_ARG_TTY_PATH ) ) != NULL
					&&	devpath[0] != '\0'
					)	)
				devname = basename( devpath );
			else
				devname = devpath = strcpy( DEV_NAME( di ), "???" );
		}
	if ( xflags & XF_TCLK ) {
		time_t t;
		time( &t );
		clk = ctime( &t ) + 11;
		clk[ 8 ] = '\0';
		}
	if ( xflags & XF_TPRG ) {
		prgargs[0] = prgarg0[0] = '\0';
		if	(	prg != 0
			||	( prg = DEV_SPID( di ) ) != 0
			||	( prg = DEV_PGRP( di ) ) != 0
				)
			prg = xflags & XF_TCMD
			?	getprg( prg, prgpath, prgarg0, prgargs )
			:	getprg( prg, prgpath, NULL, NULL );
		switch ( prg ) {
			case -1 :
				prgname = "???";
				break;
			case 0 :
				prgname = "idle";
				break;
			default :
				prgname = basename( prgpath );
			}

		if	(	xflags & XF_TCMD
			&&	prgarg0[0] == '\0'
				)
			sprintf( prgarg0, "(%s)", prgname );
		}
		
	for (;;)
		/* This loop loops exactly twice */
		switch ( c = *t++ ) {
			case '\0' :
				addchr( c );
				if ( titdst != NULL ) {
					setres( ABW_base, Pt_ARG_WINDOW_TITLE, str );
					free( str );
					return;
					}
				else
					if ( ( str = malloc( titlen ) ) != NULL ) {
						titdst = str;
						t = title;
						}
					else
						return;
				break;
			case '%' :
				switch ( c = *t++ ) {
					case 'd' : addstr( devname ); break;
					case 'u' : addstr( getlogin() ); break;
					case 'n' : addnum( DEV_NID( di ) ); break;
					case 't' : addnum( DEV_TTY( di ) ); break;
					case 'p' : addstr( devpath ); break;
					case 'T' : addstr( clk ); break;
					case 'P' : addstr( prgpath ); break;
					case 'N' : addstr( prgname ); break;
					case '$' : addnum( prg ); break;
					case '0' :
						addstr( prgarg0 );
						break;
					case 'A' :
						addstr( prgarg0 );
						addstr( " " );
					/*	no break; */
					case 'a' :
						addstr( prgargs );
						break;
					case '\0' :
						--t;
						addchr( '%' );
						break;
					default :
						addchr( c );
					}
				break;
			default :
				addchr( c );
				break;
	}		}

/*************  Base window setup: open device and start the program  ************/

static void ctset( int fd ) {
	/* Set the controlling terminal */
	pid_t const me = getpid();
	#if !defined(__QNXNTO__)
		tcsetct( fd, me );
	#endif
	tcsetpgrp( fd, me );
	}

static void errmsg( const char *fmt, ... ) {
	char buf[ 256 ];
	va_list ap;
	va_start( ap, fmt );
	PtTerminalPut( ABW_tty, buf, vsprintf( buf, fmt, ap ) );
	va_end( ap );
	}

#define SET_ARG( ptr, type, val, len ) \
	do { PtSetArg( ptr, type, val, len ); ++(ptr); } while (0)

const char default_title[] = "%d: %N";

int
base_setup( PtWidget_t *link_instance, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) {
	PtArg_t args[ 5 ]; PhEvent_t noevent;
	PtWidget_t *tty = ABW_tty;
	/* eliminate 'unreferenced' warnings */
	link_instance = link_instance, cbinfo = cbinfo;

	if ( apinfo->reason == ABR_PRE_REALIZE ) {
		PtTerminalSelection_t sel;

		PtArg_t *arg = args;

		char *tit = title;
		title = NULL; /* Suppress setting the title for a while */
		readfontsets();
		cfg2wgt();
		title = tit;

		if ( cfg.flags & FL_MSE ) {
			memset( &sel, 0, sizeof(sel) );
			sel.flags = Pt_TERM_SELECTION_ALWAYS | Pt_TERM_SELECTION_TYPE_KEEP | Pt_TERM_SELECTION_FIRST_KEEP | Pt_TERM_SELECTION_LAST_KEEP ;
			SET_ARG( arg, Pt_ARG_TERM_SELECTION, &sel, 0 );
			}

		SET_ARG( arg, Pt_ARG_TTY_FLAGS, ttyflags, TTYFLAGS );

		if ( xflags & XF_PRIUP ) {
			#if defined(__QNXNTO__)
				spawn_options.options.param.sched_priority = getprio(0) + 1;
				spawn_options.options.flags |= SPAWN_EXPLICIT_SCHED;
			#else
				spawn_options.priority = getprio(0) + 1;
			#endif
			}

		SET_ARG( arg, Pt_ARG_TTY_SPAWN_OPTIONS, &spawn_options, 0 );

		if ( xflags & XF_PAL )
			SET_ARG( arg, Pt_ARG_TERM_COLOR_TABLE, palette, PALETTE_SIZE );

		/* This one should be done within PhAB: */
		SET_ARG( arg, Pt_ARG_TERM_DRAW_MODES, ~0u, Pt_TERM_SCROLL_NOVISCHK );
		PtSetResources( tty, arg-args, args );

		memset( &noevent, 0, sizeof(noevent) );
		PtContainerGiveFocus( tty, &noevent );
		}
	else {
		int fd;

		setcharset();
		switch ( devopt ) {
		/*	case 'R' : */
			default :
				PtSetArg( args, Pt_ARG_TTY_PSEUDO, device, 0 );
				break;
			case 'd' :
				PtSetArg( args, Pt_ARG_TTY_PATH, device, 0 );
				break;
			case 'D' :
				PtSetArg( args, Pt_ARG_TTY_FD, fd = atoi(device), 0 );
				break;
			}
		PtSetResources( tty, 1, args );
		PtSetArg( args, Pt_ARG_TTY_MFD, 0, 0 );
		PtGetResources( tty, 1, args );
		if ( args[0].value == -1 )
			switch ( devopt ) {
				case 'D' :
					errmsg(
						"Descriptor %d is not a device ", fd
						);
					break;
				case 'd' :
					errmsg(
						"Cannot open device '%s'\n\r(%s) ",
						device, strerror(errno)
						);
					break;
				case 'R' :
					errmsg( "Cannot find a free pseudo tty " );
					if ( device )
						errmsg( "on '%s' ", device );
					break;
				}
		else {
			/* Device opened successfully */
			if ( ! in_foreground() ) {
				setpgid(0,0);
				ctset( args[0].value );
				}
			if ( shargs != NULL ) {
				setres( tty, Pt_ARG_TTY_ARGV, shargs );
				if ( getres( tty, Pt_ARG_TTY_PID ) == 0 ) {
					/* Child not spawned - close the device */
					int err = errno;
					setres( tty, Pt_ARG_TTY_FD, -1 );
					PtSetResources( tty, 1, args );
					errmsg( "Cannot start program '%s'\r\n(%s) ",
						*shargs ? *shargs : PtTtyShell(),
						strerror(err)
						);
					}
				else
					setupsignals();
			}	}

		if ( xflags & XF_PRIDN )
			setprio( 0, getprio(0) - 1 );

		if ( title == NULL )
			newstr( &title, default_title );
		check_title();

		if	(	devfd != NULL
			&&	( device = getptr( tty, Pt_ARG_TTY_PATH ) ) != NULL
				) {
			write( fd = atoi( devfd ), device, strlen(device) );
			close( fd );
		}	}

	return Pt_CONTINUE;
	}


int
destroy_base( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )
	{
	/* eliminate 'unreferenced' warnings */
	widget = widget, apinfo = apinfo, cbinfo = cbinfo;

	kill_shell();
	finish();

	return Pt_CONTINUE;
	}

