#ifdef UNIX
/*
 * Session manager and session manager switch
 *
 * The session manager switch intercepts all session and console I/O calls and
 * forwards them to session managers based on the switch entry managing the
 * session, or the system default session manager for new sessions.  New
 * sessions may also be started attached to specific session managers.
 * Sessions can also be "reparented" from one session manager to another.
 *
 * Per-session options are now supported.  Per-session-*manager* options have
 * assigned slots in the session manager definition, but are not yet supported.
 */

#include "global.h"
#include "commands.h"
#include <signal.h>
#include "hardware.h"
#include "proc.h"
#include "socket.h"
#undef tputs
#include "tty.h"
#include "sessmgr.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: sessmgr.c,v 1.21 2001/05/06 16:32:58 brian Exp $";
#endif

static int dosmstatus (int, char **, void *);
static int dosmdefault (int, char **, void *);
static int dosmcreate (int, char **, void *);
static int dosmreparent (int, char **, void *);
static int dosmoptions (int, char **, void *);

void sm_status (int pos, char *str);
extern void displayStatLine (int offset, int phase, int onlymarquee);
extern int Numrows;
extern int BLOCKStatline, INStatline;


/*
 * The session manager switch:  an array of pointers to session managers.
 */

#ifdef SM_CURSES
extern struct sessmgr_sw curses_sessmgr;
#endif
#ifdef SM_RAW
extern struct sessmgr_sw raw_sessmgr;
#endif
#ifdef SM_DUMB
extern struct sessmgr_sw dumb_sessmgr;
#endif

static struct sessmgr_sw *sessmgr_sw[] =
{
#ifdef SM_CURSES
	&curses_sessmgr,
#endif
#ifdef SM_DUMB
	&dumb_sessmgr,
#endif
#ifdef SM_RAW
	&raw_sessmgr,
#endif
	0,
};



static int sesm_initted;

/*
 * Commands for session manager administration
 */

static struct cmds SMcmds[] =
{
	{ "create",	dosmcreate,			0, 0, NULLCHAR },
	{ "default",	dosmdefault,			0, 0, NULLCHAR },
	{ "options",	dosmoptions,			0, 0, NULLCHAR },
	{ "reparent",	dosmreparent,			0, 0, NULLCHAR },
	{ "status",	dosmstatus,			0, 0, NULLCHAR },
	{ NULLCHAR,	NULLFP ((int, char **, void *)),0, 0, NULLCHAR }
};


static struct cmds SMsessions[] =
{
#ifdef MAILBOX
	{ "bbs",	dobbs, 	     1024, 0, NULLCHAR },
#endif
#ifdef AX25
#ifdef ALLSESSIONS
	{ "connect",	doconnect,   1024, 3, "connect <interface> <callsign>" },
#endif
#endif
#ifdef ALLCMD
	{ "dir",	dodir, 		0, 0, NULLCHAR },
#endif
#ifdef DIALER
	{ "dialer",	dodialer,     512, 2, "dialer <iface> [<file> [<seconds> [<pings> [<hostid>]]]]" },
#endif
#ifdef ALLCMD
	{ "finger",	dofinger,    1024, 2, "finger name[@host]" },
#endif
#ifdef ALLSESSIONS
	{ "ftp",	doftp, 	     2048, 2, "ftp <address>" },
#endif
#ifdef ALLCMD
	{ "more",	domore,		0, 2, "more <file> [searchstring]" },
#endif
	{ "ping",	doping,       512, 4, "ping <hostid> <length> <interval> [incflag]" },
#ifdef RLOGINCLI
	{ "rlogin",	dorlogin,    2048, 2, "rlogin <address>" },
#endif
#if defined(AX25) && defined(ALLSESSIONS) && defined(ALLSERV)
	{ "split",	doconnect,   1024, 3, "split <interface> <callsign>" },
#endif
#ifdef ALLSESSIONS
	{ "telnet",	dotelnet,    1024, 2, "telnet <address> [port]" },
#endif
#ifdef ALLSERV
	{ "ttylink",	dotelnet,    1024, 2, "ttylink <address> [port]" },
#endif
	{ NULLCHAR,	NULLFP ((int, char **, void *)),
					0, 0, NULLCHAR },
};

#if 0
extern int STATLINE;
#endif



/*
 * Used by the newscreen() entry point, the creation of the trace session
 * in main(), and by reparenting.
 */

static void
sm_newscreen (const char *sc, struct session *sp)
{
struct sessmgr_sw *sm;
const char *cp;

	if ((sm = sm_lookup (sc, &cp)) == (struct sessmgr_sw *) 0) {
		sm = sessmgr_sw[0];	/* system-wide default session manager */
		cp = 0;
	}
	if (!sp)
		return;
	sp->screen = mallocw (sizeof *sp->screen);
	if (!(sm->flags & SM_INIT)) {
		if (Current && Current != sp && Current->screen &&
		    !(Current->screen->sessmgr->flags & SM_SUSPEND)) {
			(*Current->screen->sessmgr->suspend) (Current->screen->sessmgr);
			Current->screen->sessmgr->flags |= SM_SUSPEND;
		}
		(void) (*sm->init) (sm);
		sm->flags |= SM_INIT;
	}
	sm->refcnt++;
	sp->screen->sessmgr = sm;
	sp->screen->sesmdat =
		(*sm->create) (sm, sp);
	if (sm->sessopt && cp)
		(void) (*sm->sessopt) (sm, sp->screen->sesmdat, cp);
	sp->screen->flags = 0;
#if 0
	sp->screen->statline = STATLINE;
#else
	sp->screen->statline = 0;
#endif
	if (sp->split)
		sp->screen->flags |= SMS_SPLIT;
	if (!(sm->flags & SM_SPLIT))
		sp->split = 0;	/* if sm can't do split, split sessions are deranged */
	sp->screen->next_sm = strdup (sc);
	sp->screen->use_sm = sp->screen->next_sm;
	if (sm->flags & SM_SUSPEND) {
		(*Current->screen->sessmgr->resume) (Current->screen->sessmgr);
		Current->screen->sessmgr->flags &= ~SM_SUSPEND;
	}
#if 0
	/* this is ugly... */
	/* (it's also wrong; figure it out later...) */
	if (sp->type == TRACESESSION)
		sp->screen->flags |= SMS_DISCARD;
#endif
}



int
dosessmgr (int argc, char **argv, void *p)
{
	if (argc < 2)
		return dosmstatus (argc, argv, p);
	return subcmd (SMcmds, argc, argv, p);
}



static int
dosmstatus (int argc OPTIONAL, char **argv OPTIONAL, void *p OPTIONAL)
{
struct screen *sp;
char *cp;
int s;

	tprintf ("Known session managers are:\n");
	for (s = 0; sessmgr_sw[s]; s++)
		tprintf ("%s\n", sessmgr_sw[s]->name);
	tprintf ("\n");
	(void) dosmdefault (0, 0, 0);
	tprintf ("\n#  Type         Name         Process      Manager  Options\n");
	tprintf ("== ============ ============ ============ ======== ==========\n");
	for (s = 0; (unsigned) s < Nsessions; s++) {
		if (Sessions[s].type == FREE)
			continue;
		tprintf ("%-2d %-12s ", s, Sestypes[Sessions[s].type]);
		if (Sessions[s].name)
			tprintf ("%-12.12s ", Sessions[s].name);
		else
			tprintf ("             ");
		if (Sessions[s].proc->name && *Sessions[s].proc->name)
			tprintf ("%-12.12s", Sessions[s].proc->name);
		else
			tprintf ("%8.8lx    ", (uint32) Sessions[s].proc);
		tprintf (" %-8.8s", Sessions[s].screen->sessmgr->name);
		/* we don't support session MANAGER options just yet... */
		if ((sp = Sessions[s].screen)->sessmgr->sessopt &&
		    (cp = (*sp->sessmgr->sessopt) (sp->sessmgr, sp->sesmdat, (char *) 0)) != NULLCHAR)
			tprintf (" %s", cp);
		tprintf ("\n");
	}
	return 0;
}



static int
dosmdefault (int argc, char **argv, void *p OPTIONAL)
{
char const *cp;

	if (argc < 2) {
		if (Current && Current->screen && Current->type == COMMAND)
			cp = Current->screen->next_sm;
		else if (Command && Command->screen)
			cp = Command->screen->next_sm;
		else
			cp = "";
		if (!cp || !*cp)
			cp = sessmgr_sw[0]->name;
		tprintf ("Session manager for new sessions is \"%s\".\n", cp);
		return 0;
	}
	if (argc > 2) {
		tprintf ("usage: sessmgr default [session-manager]\n");
		return 1;
	}
	if (!sm_lookup (argv[1], 0)) {
		tprintf ("No such session manager: \"%s\"\n", argv[1]);
		return 1;
	}
	if (Current->screen->next_sm)
		free (Current->screen->next_sm);
	Current->screen->next_sm = strdup (argv[1]);
	return 0;
}



static int
dosmcreate (int argc, char **argv, void *p)
{
int rc;

	if (argc < 3) {
		tprintf ("usage: sessmgr create session-manager command [args...]\n");
		return 1;
	}
	if (!sm_lookup (argv[1], 0)) {
		tprintf ("Unknown session manager \"%s\"\n", argv[1]);
		return 1;
	}
	if (Current->type != COMMAND)
		write (2, "WHOA! I'm not being run from a COMMAND session!\n", 48);
	Current->screen->use_sm = argv[1];
	rc = subcmd (SMsessions, argc - 1, argv + 1, p);
	return rc;
}



static int
dosmoptions (int argc, char **argv, void *p OPTIONAL)
{
struct screen *sp;
char *cp;
int i;

	if (argc < 2) {
		tprintf ("usage: sessmgr options session [options]\n");
		return 1;
	}
	if ((i = atoi (argv[1])) < 0 || (unsigned) i > Nsessions || Sessions[i].type == FREE) {
		tprintf ("Invalid session %s\n", argv[1]);
		return 1;
	}
	sp = Sessions[i].screen;
	if (argc < 3) {
		tprintf ("Options for session: ");
		if (!sp->sessmgr->sessopt)
			cp = 0;
		else
			cp = (*sp->sessmgr->sessopt) (sp->sessmgr, sp->sesmdat, (char *) 0);
		if (!cp)
			tprintf ("(none)\n");
		else
			tprintf ("%s\n", cp);
		return 0;
	}
	if (sp->sessmgr->sessopt)
		(void) (*sp->sessmgr->sessopt) (sp->sessmgr, sp->sesmdat, argv[2]);
	return 0;
}



/*
 * Administration of the session manager interface
 */

struct sessmgr_sw *
sm_lookup (const char *smname, const char **optp)
{
static char buf[1024];
char const *cp;
int i;

	if (!smname) {
		/* inherit a reasonable session manager */
		if (Current && Current->screen && Current->type == COMMAND) {
			smname = Current->screen->use_sm;
			Current->screen->use_sm = Current->screen->next_sm;
		} else if (Command && Command->screen)
			smname = Command->screen->next_sm;
		else
			smname = "";
	}

	for (i = 0, cp = smname; i < 1024 && *cp && *cp != ':'; cp++)
		buf[i++] = *cp;

	/*lint -save -e661 */
	if (i < 1024)
		buf[i] = '\0';
	/*lint -restore */
	if (*cp)
		cp++;
	else
		cp = 0;

	if (!i) {
		if (optp)	/* is this wise?  sessmgr ":opt", that is */
			*optp = cp;
		return sessmgr_sw[0];
	}
	for (i = 0; sessmgr_sw[i]; i++) {
		if (strcasecmp (sessmgr_sw[i]->name, buf) == 0) {
			if (optp)
				*optp = cp;
			return sessmgr_sw[i];
		}
	}
	if (optp)
		*optp = 0;
	return 0;
}



int
sm_blocked (struct session *sp)
{
	/* Return TRUE if the session is blocked. */
	return !(sp->screen->flags & SMS_ACTIVE) ||
	(sp->screen->sessmgr->flags & SM_SUSPEND);
}



/*
 * Session reparenting.  This is mildly interesting.
 *
 * The idea is that a session can be destroyed and recreated.  (Actually, the
 * session is retained; the screen is destroyed.)  The only fly in the oint-
 * ment is that you can't retain the contents of the screen:  some session
 * managers have no backing store ("dumb", and especially "none").
 *
 * Sessions can be reparented individually or by session manager.  Individual
 * sessions can be named if they have names.
 */

static void
sm_reparent (struct session *sp, char *new)
{
struct sessmgr_sw *old, *np;
int susp;

	if ((np = sm_lookup (new, 0)) == (struct sessmgr_sw *) 0)
		return;
	if ((old = sp->screen->sessmgr) == np)
		return;
	if (sp->screen->flags & SMS_SPLIT)	/* sp->split is 0 if s.m. won't split */
		sp->split = 1;	/* newscreen will deactivate if needed */
	susp = (old->flags & SM_STDIO) && (np->flags & SM_STDIO);
	freescreen (sp);
	if (susp && !(old->flags & SM_SUSPEND)) {
		(*old->suspend) (old);
		old->flags |= SM_SUSPEND;
	}
	sm_newscreen (new, sp);
}



static int
dosmreparent (int argc, char **argv, void *p OPTIONAL)
{
struct sessmgr_sw *old;
struct session *s;
int i, susp;

	if (argc != 3) {
		tprintf ("usage: sessmgr reparent <session> <session-manager>\n");
		return 1;
	}
	if (!sm_lookup (argv[2], 0)) {
		tprintf ("Unknown session manager \"%s\"\n", argv[2]);
		return -1;
	}
	if (isdigit (argv[1][0])) {
		if ((unsigned) (i = atoi (argv[1])) >= Nsessions || Sessions[i].type == FREE) {
			tprintf ("Invalid session number %d.\n", i);
			return 1;
		}
		sm_reparent (Sessions + i, argv[2]);
		if (Sessions + i == Current)
			Sessions[i].screen->flags |= SMS_ACTIVE;
		else {
			/* we must do this because newscreen() partially switches us */
			swapscreen (Current, Sessions + i);
			swapscreen (Sessions + i, Current);
		}
		return 0;
	}
	susp = -1;
	for (i = 0; (unsigned) i < Nsessions; i++) {
		if (Sessions[i].type == FREE)
			continue;
		if (Sessions[i].name && *Sessions[i].name &&
		    strcasecmp (Sessions[i].name, argv[1]) == 0) {
			if (susp != -1)
				break;
			susp = i;
		}
		if (Sessions[i].proc->name && *Sessions[i].proc->name &&
		    strcasecmp (Sessions[i].proc->name, argv[1]) == 0) {
			if (susp != -1)
				break;
			susp = i;
		}
		if (strcasecmp (Sestypes[Sessions[i].type], argv[1]) == 0) {
			if (susp != -1)
				break;
			susp = i;
		}
	}
	if ((unsigned) i != Nsessions) {
		tprintf ("Session ID \"%s\" is not unique\n", argv[1]);
		return 1;
	}
	if (susp != -1) {
		sm_reparent (Sessions + susp, argv[2]);
		if (Sessions + susp == Current)
			Sessions[susp].screen->flags |= SMS_ACTIVE;
		else {
			/* we must do this because newscreen() partially switches us */
			swapscreen (Current, Sessions + susp);
			swapscreen (Sessions + susp, Current);
		}
		return 0;
	}
	for (i = 0; sessmgr_sw[i]; i++) {
		if (sessmgr_sw[i]->refcnt && strcasecmp (sessmgr_sw[i]->name, argv[1]) == 0)
			break;
	}
	if (!sessmgr_sw[i]) {
		tprintf ("Can't decipher session ID \"%s\"\n", argv[1]);
		return 1;
	}
	/* reparent all of them! */
	old = sessmgr_sw[i];
	s = Current;
	for (i = 0; (unsigned) i < Nsessions; i++) {
		if (Sessions[i].screen->sessmgr != old)
			continue;
		sm_reparent (Sessions + i, argv[2]);
		swapscreen (s, Sessions + i);
		s = Sessions + i;
	}
	if (s == Current)
		Sessions[i].screen->flags |= SMS_ACTIVE;
	else
		swapscreen (s, Current);
	return 0;
}



/*
 * Ugly hack for doshell():  tell if the current session manager is SM_STDIO.
 */

int
sm_usestdio (void)
{
	return Current->screen->sessmgr->flags & SM_STDIO;
}



/*
 * Start a session with a specified session manager.  This could be static
 * since it's normally only used by the "sessmgr <sm> create" command, but
 * we also need it to start the trace session in a different session manager
 * from the default.
 *
 * This incorporates newsession().  Ultimately, newsession() should be changed
 * to call this function.
 */

struct session *
sm_newsession (const char *sc, const char *name, int type, int split)
{
struct session *sp;
int i;

	if (!sc || !*sc) {
		/*
		 * HACK - new sessions should inherit the current session if it is a
		 * COMMAND session.  Else use Command's next session, unless Command
		 * doesn't exist yet (creation of Command or Trace) in which case the
		 * default session manager is used.
		 *
		 * Maybe this isn't really a hack... but it feels like one.
		 */
		if (Current && Current->screen && Current->type == COMMAND)
			sc = Current->screen->next_sm;
		else if (Command && Command->screen)
			sc = Command->screen->next_sm;
		else
			sc = "";
	}
	if (type == TRACESESSION)
		i = (int) (Nsessions - 1);
	else {
		for (i = 0; (unsigned) i < Nsessions; i++) {
			if (Sessions[i].type == FREE)
				break;
		}
	}
	if ((unsigned) i == Nsessions)
		return NULLSESSION;
	sp = Sessions + i;
	sp->type = type;
	sp->s = -1;
	if (name != NULLCHAR)
		sp->name = strdup (name);
	sp->proc = Curproc;
	/* update Curproc's session pointer as well! */
	/* (in theory this could leave a dangling session...?) */
	Curproc->session = sp;
	/* Create standard input and output sockets. Output is
	 * translated to local end-of-line by default
	 */
	Curproc->input = sp->input = socket (AF_LOCAL, SOCK_STREAM, 0);
	(void) seteol (Curproc->input, Eol);
	(void) sockmode (Curproc->input, SOCK_BINARY);
	Curproc->output = sp->output = socket (AF_LOCAL, SOCK_STREAM, 0);
	(void) seteol (Curproc->output, Eol);
	(void) sockmode (Curproc->output, SOCK_ASCII);
	/* on by default */
	sp->ttystate.crnl = sp->ttystate.edit = sp->ttystate.echo = 1;
	sp->flowmode = 0;	/* Off by default */
	sp->row = Numrows;
	sp->morewait = 0;
	sp->split = split;
	sp->tsavex = sp->tsavey = 1;
	sm_newscreen (sc, sp);
	swapscreen (Current, sp);
	Current = sp;
	return sp;
}



/*
 * Session manager initiation and cleanup.
 */

void
ioinit (int no_itimer)
{
	init_sys (no_itimer);
	sesm_initted = 1;
}



void
iostop (void)
{
int c;

	sesm_initted = 0;
	for (c = 0; sessmgr_sw[c]; c++) {
		if (sessmgr_sw[c]->flags & SM_INIT)
			(*sessmgr_sw[c]->end) (sessmgr_sw[c]);
	}
	deinit_sys ();
}



/*
 * Suspend and resume now only suspend/resume the current session manager.
 * It is assumed that the session manger is SM_STDIO and that an outside
 * process needs to intercept the SM_STDIO interface temporarily.
 */

void
iosuspend (void)
{
register struct screen *sp = Current->screen;

	if ((sp->sessmgr->flags & (SM_INIT | SM_SUSPEND)) == SM_INIT) {
		(*sp->sessmgr->suspend) (sp->sessmgr);
		sp->sessmgr->flags |= SM_SUSPEND;
	}
}



void
ioresume (void)
{
register struct screen *sp = Current->screen;

	if ((sp->sessmgr->flags & (SM_INIT | SM_SUSPEND)) == (SM_INIT | SM_SUSPEND)) {
		(*sp->sessmgr->resume) (sp->sessmgr);
		sp->sessmgr->flags &= ~SM_SUSPEND;
	}
}



/*
 * Session creation, destruction, and swapping.
 */

void
j_newscreen (struct session *sp)
{
char const *sc;

	if (Current && Current->screen && Current->type == COMMAND) {
		sc = Current->screen->use_sm;
		Current->screen->use_sm = Current->screen->next_sm;
	} else if (Command && Command->screen)
		sc = Command->screen->next_sm;
	else
		sc = "";
	sm_newscreen (sc, sp);
}



void
freescreen (struct session *sp)
{
	if (!sp || !sp->screen)
		return;
	(*sp->screen->sessmgr->destroy) (sp->screen->sessmgr, sp->screen->sesmdat);
	free (sp->screen->next_sm);
	if (!--sp->screen->sessmgr->refcnt) {
		(*sp->screen->sessmgr->end) (sp->screen->sessmgr);
		sp->screen->sessmgr->flags &= ~SM_INIT;
	}
	free (sp->screen);
	sp->screen = 0;
}

extern struct session *ScreenOwner;	/* Session currently displayed */



void
swapscreen (struct session *old, struct session *new)
{
	if (old == new)
		return;
#if 1
	BLOCKStatline += 1;
#endif
#if 1
	while (INStatline)
		kwait (NULL);
#endif
	/*
	 * If they're in different session managers, swap each to/from NULL.
	 * Otherwise, swap them in the common session manager.
	 */
	if (old && new && old->screen->sessmgr == new->screen->sessmgr) {
		if (!(*old->screen->sessmgr->swtch) (old->screen->sessmgr,
		     old->screen->sesmdat, new->screen->sesmdat))
			old->screen->flags &= ~SMS_ACTIVE;
	} else {
		if (old)
			(void) (*old->screen->sessmgr->swtch) (old->screen->sessmgr, old->screen->sesmdat, 0);

		if (old && new && (old->screen->sessmgr->flags & SM_STDIO) &&
		    (new->screen->sessmgr->flags & SM_STDIO) &&
		    !(old->screen->sessmgr->flags & SM_SUSPEND)) {
			(*old->screen->sessmgr->suspend) (old->screen->sessmgr);
			old->screen->sessmgr->flags |= SM_SUSPEND;
		}
		if (new &&
		    ((new->screen->sessmgr->flags & (SM_SUSPEND | SM_STDIO | SM_INIT)) ==
		     (SM_SUSPEND | SM_STDIO | SM_INIT))) {
			(*new->screen->sessmgr->resume) (new->screen->sessmgr);
			new->screen->sessmgr->flags &= ~SM_SUSPEND;
		}
		if (new)
			(void) (*new->screen->sessmgr->swtch) (new->screen->sessmgr, 0, new->screen->sesmdat);
	}
	ScreenOwner = new;
#if 1
	BLOCKStatline -= 1;
#endif
	if (new) {
		new->screen->flags |= SMS_ACTIVE;
#if 1
		displayStatLine (0, 1, 0);
#endif
		ksignal (new->screen, 0);
	}
}



/*
 * Some session managers (e.g. curses) aren't re-entrant.  We deal with this
 * by locking individual screens.  (we assume the session manager is reentrant
 * between separate screens; this may be unsafe...)
 */

static int
LOCKSCR (register struct screen *sc, int wait)
{
sigset_t s, t;

	(void) sigfillset (&s);
	(void) sigprocmask (SIG_BLOCK, &s, &t);
	while (sc->flags & SM_LOCK) {
		if (!wait)
			return 0;
		kwait (&sc->flags);
	}
	sc->flags |= SM_LOCK;
	(void) sigprocmask (SIG_SETMASK, &t, 0);
	return 1;
}



static void
UNLOCKSCR (register struct screen *sc)
{
sigset_t s, t;

	(void) sigfillset (&s);
	(void) sigprocmask (SIG_BLOCK, &s, &t);
	sc->flags &= ~SM_LOCK;
	ksignal (&sc->flags, 0);
	(void) sigprocmask (SIG_SETMASK, &t, 0);
}



/*
 * I/O routines come in two flavors.  The internal ones, which start with
 * "s", are used by rflush() and by the external ones.  The external ones are
 * used by the rest of NOS.
 */

static void
sputch (struct session *s, int c)
{
	(*s->screen->sessmgr->putch) (s->screen->sessmgr, s->screen->sesmdat, c);
}



static void
scputs (struct session *s, const char *str)
{
register struct screen *sp;

	sp = s->screen;
	while (*str)
		(*sp->sessmgr->putch) (sp->sessmgr, sp->sesmdat, *str++);
}



static void
sclreol (struct session *s)
{
	(*s->screen->sessmgr->clreol) (s->screen->sessmgr, s->screen->sesmdat);
}



/*
 * rflush() used to flush data to the display process.  Now it does I/O itself;
 * the display process is defunct.
 */

void
rflush (void)
{
struct session *sp;
int i, c, statsize;

	if (!Sessions || !sesm_initted)
		return;		/* timer could tick before we exist, or while suspended */
#ifdef SCREENSAVER
	if (!ssenabled ())
#endif
	{
		for (sp = Sessions, i = 0; (unsigned) i < Nsessions; sp++, i++) {
			if (sp->type == FREE)
				continue;
			if (!sp->screen || !sp->screen->sessmgr || !sp->screen->sesmdat)
				continue;
			if (!(sp->screen->flags & SMS_ACTIVE) || sp->morewait == 1)
				continue;
			/* err, do I want to do this? */
			if (sp->screen->sessmgr->flags & SM_SUSPEND)
				continue;
#ifdef SCREENSAVER
			if (ssenabled ())
				continue;
#endif

			if (!LOCKSCR (sp->screen, 0)) {
				write (2, "screen not responding - still trying\r\n", 38);
				continue;
			}
			(*sp->screen->sessmgr->rflush) (sp->screen->sessmgr,
							sp->screen->sesmdat);
			if (sp->morewait == 2) {
				sp->morewait = 0;
				sputch (sp, '\r');
				sclreol (sp);
			}
			while (socklen (sp->output, 0) > 0) {
				if ((c = rrecvchar (sp->output)) == EOF)
					continue;
				if (!sp->split || c != 0x0a)
					sputch (sp, c);
				else {
					scputs (sp, Eol);
					sclreol (sp);
				}
				if (sp->record != NULLFILE) {
					if (c == '\r' || c == '\n')
						(void) fflush (sp->record);
					if (c != '\r' || sockmode (sp->output, -1) != SOCK_ASCII)
						(void) putc (c, sp->record);
				}
				statsize = 0;
				if (sp->screen->statline)
					statsize = ((sp == Command) ? getStatlines() : 1);

				if (sp->flowmode && c == '\n' && sp->row > 0 && --sp->row == statsize) {
					scputs (sp, "--More--");
					sp->morewait = 1;
					sp->row = 0;
					break;
				}
			}
			(*sp->screen->sessmgr->flush) (sp->screen->sessmgr, sp->screen->sesmdat);
			UNLOCKSCR (sp->screen);
		}
	}
}



/*
 * Public output routines.  getcursess() attempts to intuit the correct session
 * for output.
 */

static struct session *
getcursess (void)
{
static struct proc *kbproc = NULLPROC;

	/* this is a horrible hack that will (MUST!) go away in the future */
	if (!kbproc && Curproc && strcmp (Curproc->name, "keyboard") == 0)
		kbproc = Curproc;
	if (kbproc == Curproc)
		return Current;
	/*
	 * if Curproc's session isn't active we must block...
	 * N.B. should handle trace with a special flag indicating that the
	 * session should discard output instead of blocking
	 */
	if (!(Curproc->session->screen->flags & SMS_ACTIVE)) {
#if 0
		if (Curproc->session->screen->flags & SMS_DISCARD)
			return 0;
#endif
		kwait (Current->screen);
	}
#if 1
	return Current;
#else
	return Curproc->session;
#endif
}



void
putch (int c)
{
struct session *sp;

	sp = getcursess ();
	(void) LOCKSCR (sp->screen, 1);
	sputch (sp, c);
	UNLOCKSCR (sp->screen);
}



void
cputs (const char *s)
{
struct session *sp;

	sp = getcursess ();
	(void) LOCKSCR (sp->screen, 1);
	scputs (sp, s);
	UNLOCKSCR (sp->screen);
}



void
clreol (void)
{
struct session *sp;

	sp = getcursess ();
	(void) LOCKSCR (sp->screen, 1);
	sclreol (sp);
	UNLOCKSCR (sp->screen);
}



void
clrscr (void)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->clrscr) (sp->sessmgr, sp->sesmdat);
	UNLOCKSCR (sp);
}



int
wherex (void)
{
register struct screen *sp;
int i;

	sp = getcursess ()->screen;
	if (sp->sessmgr->wherex) {
		(void) LOCKSCR (sp, 1);
		i = (*sp->sessmgr->wherex) (sp->sessmgr, sp->sesmdat);
		UNLOCKSCR (sp);
		return i;
	}
	return -1;
}



int
wherey (void)
{
register struct screen *sp;
int i;

	sp = getcursess ()->screen;
	if (sp->sessmgr->wherey) {
		(void) LOCKSCR (sp, 1);
		i = (*sp->sessmgr->wherey) (sp->sessmgr, sp->sesmdat);
		UNLOCKSCR (sp);
		return i;
	}
	return -1;
}



void
window (int x1, int y1, int x2, int y2)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	if (sp->sessmgr->window) {
		(void) LOCKSCR (sp, 1);
		(*sp->sessmgr->window) (sp->sessmgr, sp->sesmdat, x1, y1, x2, y2);
		UNLOCKSCR (sp);
	}
}



void
gotoxy (int x, int y)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->gotoxy) (sp->sessmgr, sp->sesmdat, x, y);
	UNLOCKSCR (sp);
}



void
highvideo (void)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->high) (sp->sessmgr, sp->sesmdat);
	UNLOCKSCR (sp);
}



void
normvideo (void)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->norm) (sp->sessmgr, sp->sesmdat);
	UNLOCKSCR (sp);
}


void
textbackground (int color)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->bground) (sp->sessmgr, sp->sesmdat, color);
	UNLOCKSCR (sp);
}


void
textcolor (int color)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->fground) (sp->sessmgr, sp->sesmdat, color);
	UNLOCKSCR (sp);
}


void
textattr (int color)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->txtattr) (sp->sessmgr, sp->sesmdat, color);
	UNLOCKSCR (sp);
}


void
textrefresh (void)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	(void) LOCKSCR (sp, 1);
	(*sp->sessmgr->refresh) (sp->sessmgr, sp->sesmdat);
	UNLOCKSCR (sp);
}


void
_setcursortype (int t)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	if (sp->sessmgr->cursor) {
		(void) LOCKSCR (sp, 1);
		(*sp->sessmgr->cursor) (sp->sessmgr, sp->sesmdat, t);
		UNLOCKSCR (sp);
	}
}



int
kbread (void)
{
register struct screen *sp;

	sp = getcursess ()->screen;
	return (*sp->sessmgr->kbread) (sp->sessmgr, sp->sesmdat);
}



/*
 * The status entry point is different:  any routine may call the status output
 * routine, which calls the status switch entry for the Command screen.  If
 * that is NULL, we perform direct output without passing information to
 * rflush(); this avoids the flow control problems which have plagued previous
 * versions, although it requires that we duplicate parts of rflush() here
 * (and it's ugly).
 *
 * The numeric argument is presently ignored.  Eventually I may use it for
 * a positioning hint.
 */

void
sm_status (int pos, char *str)
{
struct screen *cs;

	cs = Command->screen;
	if (cs->sessmgr->status)
		(*cs->sessmgr->status) (cs->sessmgr, cs->sesmdat, pos, str);
	else {
		if (cs->sessmgr->flags & SM_SUSPEND)
			return;
		if (!(cs->flags & SMS_ACTIVE))
			return;
		(void) LOCKSCR (cs, 1);
		(*cs->sessmgr->rflush) (cs->sessmgr, cs->sesmdat);
		/* should translate \n to Eol */
		scputs (Command, str);
#if 0
		scputs (Command, Eol);
#endif
		(*cs->sessmgr->flush) (cs->sessmgr, cs->sesmdat);
		UNLOCKSCR (cs);
	}
}

#endif 		/* UNIX */
