/*
 * 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 "hardware.h"
#include "proc.h"
#include "socket.h"
#undef tputs
#include "tty.h"
#include "sessmgr.h"
#include "cmdparse.h"
#include "commands.h"
#include "config.h"

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

/*
 * 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 HOPCHECK
    /* hopcheck - need to restrict to hopcheck itself */
#endif
#ifdef ALLCMD
    "more",	domore,		0,	2,	"more <file> [searchstring]",
#endif
#ifdef NETROM
    /* netrom connect */
#endif
    "ping",	doping,		512,	4,
	"ping <hostid> <length> <interval> [incflag]",
#ifdef RLOGINCLI
    "rlogin",	dorlogin,	2048,	2,	"rlogin <address>",
#endif
#ifdef AX25
#ifdef ALLSESSIONS
#ifdef ALLSERV
    "split",	doconnect,	1024,	3,
	"split <interface> <callsign>",
#endif
#endif
#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,
};

extern int STATLINE;

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

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

    if (!(sm = sm_lookup(sc, &cp)))
    {
	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;
	}
	(*sm->init)(sm);
	sm->flags |= SM_INIT;
    }
    sm->refcnt++;
    sp->screen->sessmgr = sm;
    sp->screen->sesmdat =
	(*sm->create)(sm, sp);
    if (sm->sessopt && cp)
	(*sm->sessopt)(sm, sp->screen->sesmdat, cp);
    sp->screen->flags = 0;
    sp->screen->statline = STATLINE;
    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);
}

int
dosmstatus(int argc, char **argv, void *p)
{
    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");
    dosmdefault(0, 0, 0);
    tprintf("\n#  Type         Name         Process      Manager  Options\n");
    tprintf("== ============ ============ ============ ======== ==========\n");
    for (s = 0; 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    ", FP_SEG(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)))
	    tprintf(" %s", cp);
	tprintf("\n");
    }
    return 0;
}

int
dosmdefault(int argc, char **argv, void *p)
{
    char *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;
}

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;
}

int
dosmoptions(int argc, char **argv, void *p)
{
    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 || 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)
	    cp = "(none)";
	tprintf("%s\n", cp);
	return 0;
    }
    if (sp->sessmgr->sessopt)
	(*sp->sessmgr->sessopt)(sp->sessmgr, sp, argv[2]);
    return 0;
}

/*
 * Administration of the session manager interface
 */

struct sessmgr_sw *
sm_lookup(char *smname, char **optp)
{
    static char buf[1024];
    char *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 = "";
    }
    i = 0;
    for (cp = smname; *cp && *cp != ':'; cp++)
	buf[i++] = *cp;
    buf[i] = '\0';
    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)))
	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);
}

int
dosmreparent(int argc, char **argv, void *p)
{
    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 ((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; 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 (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; 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(char *sc, char *name, int type, int split)
{
    extern int Numrows;
    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 = Nsessions - 1;
    else
    {
	for (i = 0; i < Nsessions; i++)
	{
	    if (Sessions[i].type == FREE)
		break;
	}
    }
    if (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);
    seteol(Curproc->input, Eol);
    sockmode(Curproc->input, SOCK_BINARY);
    Curproc->output = sp->output = socket(AF_LOCAL, SOCK_STREAM, 0);
    seteol(Curproc->output, Eol);
    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;
    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 *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 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)
	{
	    (*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)
	{
	    (*new->screen->sessmgr->swtch)(new->screen->sessmgr, 0,
					   new->screen->sesmdat);
	}
    }
    ScreenOwner = new;
    if (new)
    {
	new->screen->flags |= SMS_ACTIVE;
	j_psignal(new->screen, 0);
	displayStatLine (0, 1);
    }
}

/*
 * 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
#ifdef __STDC__
LOCKSCR(register struct screen *sc, int wait)
#else
LOCKSCR(sc, wait)
    register struct screen *sc;
    int wait;
#endif
{
    sigset_t s, t;

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

static void
#ifdef __STDC__
UNLOCKSCR(register struct screen *sc)
#else
UNLOCKSCR(sc)
    register struct screen *sc;
#endif
{
    sigset_t s, t;

    sigfillset(&s);
    sigprocmask(SIG_BLOCK, &s, &t);
    sc->flags &= ~SM_LOCK;
    j_psignal(&sc->flags, 0);
    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, 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;

    if (!Sessions || !sesm_initted)
	return;	/* timer could tick before we exist, or while suspended */
#ifdef SCREENSAVER
  if (!ssenabled())	{
#endif
    for (sp = Sessions, i = 0; 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;
	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)) == -1)
		continue;
	    if (!sp->split || c != 0x0a)
		sputch(sp, c);
	    else
	    {
		scputs(sp, Eol);
		sclreol(sp);
	    }
	    if (sp->record != NULLFILE)
	    {
		if (c == '\r' || c == '\n')
		    fflush(sp->record);
		if (c != '\r' || sockmode(sp->output, -1) != SOCK_ASCII)
		    putc(c, sp->record);
	    }
	    if (sp->flowmode && c == '\n' && sp->row > 0 && --sp->row == 0)
	    {
		scputs(sp, "--More--");
		sp->morewait = 1;
		break;
	    }
	}
	(*sp->screen->sessmgr->flush)(sp->screen->sessmgr,
				      sp->screen->sesmdat);
	UNLOCKSCR(sp->screen);
    }
#ifdef SCREENSAVER
  }
#endif
}

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

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

    /* 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
	pwait(Current->screen);
    }
    return Curproc->session;
}

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

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

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

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

void
clreol(void)
{
    struct session *sp;

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

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

    sp = getcursess()->screen;
    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)
    {
	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)
    {
	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)
    {
	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;
    LOCKSCR(sp, 1);
    (*sp->sessmgr->gotoxy)(sp->sessmgr, sp->sesmdat, x, y);
    UNLOCKSCR(sp);
}

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

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

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

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

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

    sp = getcursess()->screen;
    if (sp->sessmgr->cursor)
    {
	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;
	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);
    }
}
