[comp.sources.unix] v10i096: BSD multi-screen manager, Part02/02

rs@uunet.UU.NET (Rich Salz) (08/07/87)

Submitted-by: Oliver Laumann <seismo!tub!net>
Posting-number: Volume 10, Issue 96
Archive-name: screen/Part02

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	screen.1
#	screen.c
export PATH; PATH=/bin:$PATH
echo shar: extracting "'screen.1'" '(17895 characters)'
if test -f 'screen.1'
then
	echo shar: will not over-write existing file "'screen.1'"
else
cat << \SHAR_EOF > 'screen.1'
.if n .ds Q \&"
.if n .ds U \&"
.if t .ds Q ``
.if t .ds U ''
.TH SCREEN 1 "2 March 1987"
.UC 4
.SH NAME
screen \- screen manager with VT100/ANSI terminal emulation
.SH SYNOPSIS
.B screen
[
.B \-a
] [
.B \-f
] [
.B \-n
] [
.B \-e\fIxy\fP
] [
.B \fIcmd args\fP ]
.ta .5i 1.8i
.SH DESCRIPTION
.I screen
is a full-screen window manager that
multiplexes a physical terminal between several processes (typically
interactive shells).  Each virtual terminal provides the functions
of the DEC VT100 terminal and, in addition, several control functions
from the ANSI X3.64 (ISO 6429) standard (e.g. insert/delete line).
.PP
When
.I screen
is called, it creates a single window with a shell; the pathname of the
shell is taken from the environment symbol $SHELL; if this is not
defined, \*Q/bin/sh\*U is used.
New windows can be created at any time by calling
.I screen
from within a previously created window.
The program to be started in a newly created
window and optional arguments to the program can be supplied when
.I screen
is invoked.
For instance,
.IP
screen csh
.PP
will create a window with a C-Shell and switch to that window.
When the process associated with the currently displayed window
terminates (e.g. ^D has been typed to a shell),
.I screen
switches to the previously displayed window;
when no more windows are left,
.I screen
exits.
.PP
When \*Q/etc/utmp\*U is writable by
.IR screen ,
an appropriate record is written to this file for each window and
removed when the window is terminated.
.SH "COMMAND KEYS"
The standard way to create a new window is to type \*QC-a c\*U (the notation
\*QC-x\*U will be used as a shorthand for Control-x in this manual; x is
an arbitrary letter).
\*QC-a c\*U creates a new window running a shell and switches to that
window immediately, regardless of the state of the process running
in the current window.
.I Screen
recognizes several such commands; each command consists of
\*QC-a\*U followed by a one-letter function.
For convenience, the letter after a \*QC-a\*U can be entered both with or
without the control key pressed (with the exception of
\*QC-a C-a\*U and \*QC-a a\*U; see below), thus, \*QC-a c\*U as well as
\*QC-a C-c\*U can be used to create a window.
.PP
The following commands are recognized by
.IR screen :
.IP "\fBC-a c\fP or \fBC-a C-c\fP"
Create a new window with a shell and switch to that window.
.IP "\fBC-a k\fP or \fBC-a C-k\fP"
Kill the current window and switch to the previously displayed window.
.IP "\fBC-a C-\e\fP"
Kill all windows and terminate
.IR screen .
.IP "\fBC-a C-a\fP\0\0\0\0\0"
Switch to the previously displayed window.
.IP "\fBC-a 0\fP to \fBC-a 9\fP"
Switch to the window with the number 0 (1, 2, .., 9, respectively).
When a new window is established, the first available number from the
range 0..9 is assigned to this window.
Thus, the first window can be activated by \*QC-a 0\*U; at most
10 windows can be present at any time.
.IP "\fBC-a space\fP or \fBC-a C-space\fP or \fBC-a n\fP or \fBC-a C-n\fP"
Switch to the next window.  This function can be used repeatedly to
cycle through the list of windows.
(Control-space is not supported by all terminals.)
.IP "\fBC-a p\fP or \fBC-a C-p\fP or \fBC-a -\fP"
Switch to the previous window (the opposite of \fBC-a space\fP).
.IP "\fBC-a l\fP or \fBC-a C-l\fP"
Redisplay the current window.
.IP "\fBC-a z\fP or \fBC-a C-z\fP"
Suspend
.IR screen .
.IP "\fBC-a h\fP or \fBC-a C-h\fP"
Write a hardcopy of the current window to the file \*Qhardcopy.\fIn\fP\*U
in the window's current directory,
where \fIn\fP is the number of the current window.
.IP "\fBC-a .\fP (Control-a dot)"
Write the termcap entry for the virtual terminal of the currently active
window to the file \*Q.termcap\*U in the directory \*Q$HOME/.screen\*U.
This termcap entry is identical to the value of the environment symbol
TERMCAP that is set up by
.I screen
for each window.
.IP "\fBC-a w\fP or \fBC-a C-w\fP"
Display a list of all windows.
For each window, the number of the window and the process that has been
started in the window is displayed; the current window is marked with a
`*'.
.IP "\fBC-a t\fP or \fBC-a C-t\fP"
Print in the message line the time of day, the host name, the load averages
over 1, 5, and 15 minutes (if this is available on your system),
the cursor position of the current window in the form \*Q(colum,row)\*U
starting with \*U(0,0)\*U, and an indication if flow control
and (for the current window)
insert mode, origin mode, wrap mode, and keypad application
mode are enabled or not (indicated by a '+' or '-').
.IP "\fBC-a v\fP or \fBC-a C-v\fP"
Display the version.
.IP "\fBC-a a\fP\0\0\0\0\0"
Send the character \*QC-a\*U to the processes running in the window.
.IP "\fBC-a s\fP or \fBC-a C-s\fP"
Send a Control-s to the program running in the window.
.IP "\fBC-a q\fP or \fBC-a C-q\fP"
Send a Control-q to the program running in the window.
.IP
.PP
The
.B -e
option can be used to specify a different command character and
a character which, when typed immediately after the command character,
generates a literal command character.
The defaults for these two characters are \*QC-a\*U and `a'.
(Note that the function to switch to the previous window is actually the
command character typed twice; for instance, when
.I screen
is called with the option \*Q\fB-e]x\fP\*U (or \*Q\fB-e ]x\fP\*U),
this function becomes \*Q]]\*U).
.SH CUSTOMIZATION
When
.I screen
is invoked, it executes initialization commands from the file
\*Q.screenrc\*U in the user's home directory.
Commands in \*Q.screenrc\*U are mainly used to automatically
establish a number of windows each time
.I screen
is called, and to bind functions to specific keys.
Each line in \*Q.screenrc\*U contains one initialization command; lines
starting with `#' are ignored.
Commands can have arguments; arguments are separated by tabs and spaces
and can be surrounded by single quotes or double quotes.
.PP
The following initialization commands are recognized by
.IR screen :
.PP
.ne 3
.B "escape \fIxy\fP"
.PP
Set the command character to \fIx\fP and the character generating a literal
command character to \fIy\fP (see the -e option above).
.PP
.ne 3
.B "bell \fImessage\fP"
.PP
When a bell character is sent to a background window,
.I screen
displays a notification in the message line (see below).
The notification message can be re-defined by means of the \*Qbell\*U
command; each occurrence of `%' in \fImessage\fP is replaced by
the number of the window to which a bell has been sent.
The default message is
.PP
	Bell in window %
.PP
An empty message can be supplied to the \*Qbell\*U command to suppress
output of a message line (bell "").
.PP
.ne 3
.B "screen [\fIn\fP] [\fIcmds args\fP]"
.PP
Establish a window.
If an optional number \fIn\fP in the range 0..9 is given, the window
number \fIn\fP is assigned to the newly created window (or, if this
number is already in use, the next higher number).
Note that \fIn\fP has a value of zero for the standard shell window
created after \*Q.screenrc\*U has been read.
If a command is specified after \*Qscreen\*U, this command (with the given
arguments) is started in the window; if no command is given, a shell
is created in the window.
Thus, if your \*Q.screenrc\*U contains the lines
.PP
.nf
	# example for .screenrc:
	screen 1
	screen 2 telnet foobar
.fi
.PP
.I screen
creates a shell window (window #1), a window with a TELNET connection
to the machine foobar (window #2), and, finally, a second shell window
(the default window) which gets a window number of zero.
When the initialization is completed,
.I screen
always switches to the default window, so window #0 is displayed
when the above \*Q.screenrc\*U is used.
.PP
.ne 3
.B "chdir [\fIdirectory\fP]"
.PP
Change the \fIcurrent directory\fP of
.I screen
to the specified directory or, if called without an argument,
to the home directory (the value of the environment symbol $HOME).
All windows that are created by means of the \*Qscreen\*U command
from within \*Q.screenrc\*U or by means of \*QC-a c'' are running
in the \fIcurrent directory\fP; the \fIcurrent directory\fP is
initially the directory from which the shell command
.I screen
has been invoked.
Hardcopy files are always written to the directory in which the current
window has been created (that is, \fInot\fP in the current directory
of the shell running in the window).
.PP
.ne 3
.B "bind \fIkey\fP [\fIfunction\fP | \fIcmd args\fP]"
.PP
Bind a function to a key.
By default, each function provided by
.I screen
is bound to one or more keys as indicated by the above table, e.g. the
function to create a new window is bound to \*QC-c\*U and \*Qc\*U.
The \*Qbind\*U command can be used to redefine the key bindings and to
define new bindings.
The \fIkey\fP
argument is either a single character, a sequence of the form
\*Q^x\*U meaning \*QC-x\*U, or an octal number specifying the
ASCII code of the character.
If no further argument is given, any previously established binding
for this key is removed.
The \fIfunction\fP argument can be one of the following keywords:
.PP
.nf
	shell	Create new window with a shell
	kill	Kill the current window
	quit	Kill all windows and terminate
	other	Switch to previously displayed window
	next	Switch to the next window
	prev	Switch to the previous window
	redisplay	Redisplay current window
	hardcopy	Make hardcopy of current window
	termcap	Write termcap entry to $HOME/.screen/.termcap
	suspend	Suspend \fIscreen\fP
	windows	Display list of window
	info	Print useful information in the message line
	xon	Send Control-q
	xoff	Send Control-s
	version	Display the version
	select0	Switch to window #0
	\0\0...
	select9	Switch to window #9
.fi
.PP
In addition, a key can be bound such that a window is created running
a different command than the shell when that key is pressed.
In this case, the command optionally followed by
arguments must be given instead of one of the above-listed keywords.
For example, the commands
.PP
.nf
	bind ' ' windows
	bind ^f telnet foobar
	bind 033 su
.fi
.PP
would bind the space key to the function that displays a list
of windows (that is, the function usually invoked by \*QC-a C-w\*U
or \*QC-a w\*U would also be available as \*QC-a space\*U),
bind \*QC-f\*U to the function \*Qcreate a window with a TELNET
connection to foobar\*U, and bind \*Qescape\*U to the function
that creates a window with a super-user shell.
.SH "VIRTUAL TERMINAL"
.I Screen
prints error messages and other diagnostics in a \fImessage line\fP above
the bottom of the screen.
The message line is removed when a key is pressed or, automatically,
after a couple of seconds.
The message line facility can be used by an application running in
the current window by means of the ANSI \fIPrivacy message\fP
control sequence (for instance, from within the shell, something like
.IP
echo '^[^Hello world^[\e'   (where ^[ is an \fIescape\fP)
.PP
can be used to display a message line.
.PP
When the `NF' capability is found in the termcap entry of the
terminal on which
.I screen
has been started, flow control is turned off for the terminal.
This enables the user to send XON and XOFF characters to the
program running in a window (this is required by the \fIemacs\fP
editor, for instance).
The command line options 
.B -n
and
.B -f
can be used to turn flow control off or on, respectively, independently
of the `NF' capability.
.PP
.I
Screen
never writes in the last position of the screen, unless the boolean
capability `LP' is found in the termcap entry of the terminal.
Usually,
.I screen
cannot predict whether or not a particular terminal scrolls when
a character is written in the last column of the last line;
`LP' indicates that it is safe to write in this position.
Note that the `LP' capability is independent of `am' (automatic
margins); for certain terminals, such as the VT100, it is reasonable
to set `am' as well as `LP' in the corresponding termcap entry
(the VT100 does not move the cursor when a character is written in
the last column of each line).
.PP
.I Screen
puts into the environment of each process started in a newly created
window the symbols \*QWINDOW=\fIn\fP\*U (where \fIn\fP is the number
of the respective window), \*QTERM=screen\*U, and a TERMCAP variable
reflecting the capabilities of the virtual terminal emulated by
.IR screen .
The actual set of capabilities supported by the virtual terminal
depends on the capabilities supported by the physical terminal.
If, for instance, the physical terminal does not support standout mode,
.I screen
does not put the `so' and `se' capabilities into the window's TERMCAP
variable, accordingly. 
However, a minimum number of capabilities must be supported by a
terminal in order to run
.IR screen ,
namely scrolling, clear screen, and direct cursor addressing
(in addition,
.I screen
does not run on hardcopy terminals or on terminals that overstrike).
.PP
Some capabilities are only put into the TERMCAP
variable of the virtual terminal if they can be efficiently
implemented by the physical terminal.
For instance, `dl' (delete line) is only put into the TERMCAP
variable if the terminal supports either delete line itself or
scrolling regions.
If
.I screen
is called with the
.B -a
option, \fIall\fP capabilities are put into the environment,
even if
.I screen
must redraw parts of the display in order to implement a function.
.PP
The following is a list of control sequences recognized by
.IR screen .
\*Q(V)\*U and \*Q(A)\*U indicate VT100-specific and ANSI-specific
functions, respectively.
.PP
.nf
.TP 20
.B "ESC E"
	Next Line
.TP 20
.B "ESC D"
	Index
.TP 20
.B "ESC M"
	Reverse Index
.TP 20
.B "ESC H"
	Horizontal Tab Set
.TP 20
.B "ESC 7"
(V)	Save Cursor and attributes
.TP 20
.B "ESC 8"
(V)	Restore Cursor and Attributes
.TP 20
.B "ESC c"
	Reset to Initial State
.TP 20
.B "ESC ="
(V)	Application Keypad Mode
.TP 20
.B "ESC >"
(V)	Numeric Keypad Mode
.TP 20
.B "ESC # 8"
(V)	Fill Screen with E's
.TP 20
.B "ESC \e"
(A)	String Terminator
.TP 20
.B "ESC ^"
(A)	Privacy Message (Message Line)
.TP 20
.B "ESC P"
(A)	Device Control String (not used)
.TP 20
.B "ESC _"
(A)	Application Program Command (not used)
.TP 20
.B "ESC ]"
(A)	Operating System Command (not used)
.TP 20
.B "ESC [ Pn ; Pn H"
	Direct Cursor Addressing
.TP 20
.B "ESC [ Pn ; Pn f"
	Direct Cursor Addressing
.TP 20
.B "ESC [ Pn J"
	Erase in Display
.TP 20
\h'\w'ESC 'u'Pn = None or \fB0\fP
	From Cursor to End of Screen
.TP 20
\h'\w'ESC 'u'\fB1\fP
	From Beginning of Screen to Cursor
.TP 20
\h'\w'ESC 'u'\fB2\fP
	Entire Screen
.TP 20
.B "ESC [ Pn K"
	Erase in Line
.TP 20
\h'\w'ESC 'u'Pn = None or \fB0\fP
	From Cursor to End of Line
.TP 20
\h'\w'ESC 'u'\fB1\fP
	From Beginning of Line to Cursor
.TP 20
\h'\w'ESC 'u'\fB2\fP
	Entire Line
.TP 20
.B "ESC [ Pn A"
	Cursor Up
.TP 20
.B "ESC [ Pn B"
	Cursor Down
.TP 20
.B "ESC [ Pn C"
	Cursor Right
.TP 20
.B "ESC [ Pn D"
	Cursor Left
.TP 20
.B "ESC [ Ps ;...; Ps m"
	Select Graphic Rendition
.TP 20
\h'\w'ESC 'u'Ps = None or \fB0\fP
	Default Rendition
.TP 20
\h'\w'ESC 'u'\fB1\fP
	Bold
.TP 20
\h'\w'ESC 'u'\fB2\fP
(A)	Faint
.TP 20
\h'\w'ESC 'u'\fB3\fP
(A)	\fIStandout\fP Mode (ANSI: Italicised)
.TP 20
\h'\w'ESC 'u'\fB4\fP
	Underlined
.TP 20
\h'\w'ESC 'u'\fB5\fP
	Blinking
.TP 20
\h'\w'ESC 'u'\fB7\fP
	Negative Image
.TP 20
\h'\w'ESC 'u'\fB22\fP
(A)	Normal Intensity
.TP 20
\h'\w'ESC 'u'\fB23\fP
(A)	\fIStandout\fP Mode off (ANSI: Italicised off)
.TP 20
\h'\w'ESC 'u'\fB24\fP
(A)	Not Underlined
.TP 20
\h'\w'ESC 'u'\fB25\fP
(A)	Not Blinking
.TP 20
\h'\w'ESC 'u'\fB27\fP
(A)	Positive Image
.TP 20
.B "ESC [ Pn g"
	Tab Clear
.TP 20
\h'\w'ESC 'u'Pn = None or \fB0\fP
	Clear Tab at Current Position
.TP 20
\h'\w'ESC 'u'\fB3\fP
	Clear All Tabs
.TP 20
.B "ESC [ Pn ; Pn r"
(V)	Set Scrolling Region
.TP 20
.B "ESC [ Pn I"
(A)	Horizontal Tab
.TP 20
.B "ESC [ Pn Z"
(A)	Backward Tab
.TP 20
.B "ESC [ Pn L"
(A)	Insert Line
.TP 20
.B "ESC [ Pn M"
(A)	Delete Line
.TP 20
.B "ESC [ Pn @"
(A)	Insert Character
.TP 20
.B "ESC [ Pn P"
(A)	Delete Character
.TP 20
.B "ESC [ Ps  ;...; Ps h"
	Set Mode
.TP 20
.B "ESC [ Ps  ;...; Ps l"
	Reset Mode
.TP 20
\h'\w'ESC 'u'Ps = \fB4\fP
(A)	Insert Mode
.TP 20
\h'\w'ESC 'u'\fB?5\fP
(V)	Visible Bell (\fIOn\fP followed by \fIOff\fP)
.TP 20
\h'\w'ESC 'u'\fB?6\fP
(V)	\fIOrigin\fP Mode
.TP 20
\h'\w'ESC 'u'\fB?7\fP
(V)	\fIWrap\fP Mode
.fi
.SH FILES
.nf
.ta 2i
$(HOME)/.screenrc	\fIscreen\fP initialization commands
.br
$(HOME)/.screen	Directory created by \fIscreen\fP
.br
$(HOME)/.screen/\fItty\fP	Socket created by \fIscreen\fP
.br
hardcopy.[0-9]	Screen images created by the hardcopy function
.br
/etc/termcap	Terminal capability data base
.br
/etc/utmp	Login records
.br
/etc/ttys	Terminal initialization data
.fi
.SH "SEE ALSO"
termcap(5), utmp(5)
.SH AUTHOR
Oliver Laumann
.SH BUGS
Standout mode is not cleared before newline or cursor addressing.
.PP
If `LP' is not set but `am' is set, the last character in the last line is never
written, and it is not correctly re-displayed when the screen is
scrolled up or when a character is deleted in the last line.
.PP
The VT100 \*Qwrap around with cursor addressing\*U bug is not compensated
when
.I screen
is running on a VT100.
.PP
`AL,' `DL', and similar parameterized capabilities are not used if present.
.PP
`dm' (delete mode), `xn', and `xs' are not handled
correctly (they are ignored). 
.PP
Different character sets are not supported.
.PP
`ms' is not advertised in the termcap entry (in order to compensate
a bug in
.IR curses (3X)).
.PP
Scrolling regions are only emulated if the physical terminal supports
scrolling regions.
.PP
.I Screen
does not make use of hardware tabs.
.PP
.I Screen
must be installed as set-uid with owner root in order to be able
to correctly change the owner of the tty device file for each
window.
Special permission may also be required to write the file \*Q/etc/utmp\*U.
.PP
Entries in \*Q/etc/utmp\*U are not removed when
.I screen
is killed with SIGKILL.
SHAR_EOF
if test 17895 -ne "`wc -c < 'screen.1'`"
then
	echo shar: error transmitting "'screen.1'" '(should have been 17895 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'screen.c'" '(32087 characters)'
if test -f 'screen.c'
then
	echo shar: will not over-write existing file "'screen.c'"
else
cat << \SHAR_EOF > 'screen.c'
/* Copyright (c) 1987, Oliver Laumann, Technical University of Berlin.
 * Not derived from licensed software.
 *
 * Permission is granted to freely use, copy, modify, and redistribute
 * this software, provided that no attempt is made to gain profit from it,
 * the author is not construed to be liable for any results of using the
 * software, alterations are clearly marked as such, and this notice is
 * not modified.
 */

static char ScreenVersion[] = "screen 1.1i  1-Jul-87";

#include <stdio.h>
#include <sgtty.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <utmp.h>
#include <pwd.h>
#include <nlist.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "screen.h"

#ifdef GETTTYENT
#   include <ttyent.h>
#else
    static struct ttyent {
	char *ty_name;
    } *getttyent();
    static char *tt, *ttnext;
    static char ttys[] = "/etc/ttys";
#endif

#define MAXWIN     10
#define MAXARGS    64
#define MAXLINE  1024
#define MSGWAIT     5

#define Ctrl(c) ((c)&037)

extern char *blank, Term[], **environ;
extern rows, cols;
extern status;
extern time_t TimeDisplayed;
extern char AnsiVersion[];
extern short ospeed;
extern flowctl;
extern errno;
extern sys_nerr;
extern char *sys_errlist[];
extern char *index(), *rindex(), *malloc(), *getenv(), *MakeTermcap();
extern char *getlogin(), *ttyname();
static Finit(), SigChld();
static char *MakeBellMsg(), *Filename(), **SaveArgs();

static char PtyName[32], TtyName[32];
static char *ShellProg;
static char *ShellArgs[2];
static char inbuf[IOSIZE];
static inlen;
static ESCseen;
static GotSignal;
static char DefaultShell[] = "/bin/sh";
static char DefaultPath[] = ":/usr/ucb:/bin:/usr/bin";
static char PtyProto[] = "/dev/ptyXY";
static char TtyProto[] = "/dev/ttyXY";
static char SockPath[512];
static char SockDir[] = ".screen";
static char *SockName;
static char *NewEnv[MAXARGS];
static char Esc = Ctrl('a');
static char MetaEsc = 'a';
static char *home;
static HasWindow;
static utmp, utmpf;
static char UtmpName[] = "/etc/utmp";
static char *LoginName;
static char *BellString = "Bell in window %";
static mflag, nflag, fflag;
static char HostName[MAXSTR];
#ifdef LOADAV
    static char KmemName[] = "/dev/kmem";
    static char UnixName[] = "/vmunix";
    static char AvenrunSym[] = "_avenrun";
    static struct nlist nl[2];
    static avenrun, kmemf;
#endif

struct mode {
    struct sgttyb m_ttyb;
    struct tchars m_tchars;
    struct ltchars m_ltchars;
    int m_ldisc;
    int m_lmode;
} OldMode, NewMode;

static struct win *curr, *other;
static CurrNum, OtherNum;
static struct win *wtab[MAXWIN];

#define MSG_CREATE    0
#define MSG_ERROR     1

struct msg {
    int type;
    union {
	struct {
	    int aflag;
	    int nargs;
	    char line[MAXLINE];
	    char dir[1024];
	} create;
	char message[MAXLINE];
    } m;
};

#define KEY_IGNORE         0
#define KEY_HARDCOPY       1
#define KEY_SUSPEND        2
#define KEY_SHELL          3
#define KEY_NEXT           4
#define KEY_PREV           5
#define KEY_KILL           6
#define KEY_REDISPLAY      7
#define KEY_WINDOWS        8
#define KEY_VERSION        9
#define KEY_OTHER         10
#define KEY_0             11
#define KEY_1             12
#define KEY_2             13
#define KEY_3             14
#define KEY_4             15
#define KEY_5             16
#define KEY_6             17
#define KEY_7             18
#define KEY_8             19
#define KEY_9             20
#define KEY_XON           21
#define KEY_XOFF          22
#define KEY_INFO          23
#define KEY_TERMCAP       24
#define KEY_QUIT          25
#define KEY_CREATE        26

struct key {
    int type;
    char **args;
} ktab[256];

char *KeyNames[] = {
    "hardcopy", "suspend", "shell", "next", "prev", "kill", "redisplay",
    "windows", "version", "other", "select0", "select1", "select2", "select3",
    "select4", "select5", "select6", "select7", "select8", "select9",
    "xon", "xoff", "info", "termcap", "quit",
    0
};

main (ac, av) char **av; {
    register n, len;
    register struct win **pp, *p;
    char *ap;
    int s, r, w, x = 0;
    int aflag = 0;
    struct timeval tv;
    time_t now;
    char buf[IOSIZE], *myname = (ac == 0) ? "screen" : av[0];
    char rc[256];

    while (ac > 0) {
	ap = *++av;
	if (--ac > 0 && *ap == '-') {
	    switch (ap[1]) {
	    case 'c':        /* Compatibility with older versions. */
		break;
	    case 'a':
		aflag = 1;
		break;
	    case 'm':
		mflag = 1;
		break;
	    case 'n':
		nflag = 1;
		break;
	    case 'f':
		fflag = 1;
		break;
	    case 'e':
		if (ap[2]) {
		    ap += 2;
		} else {
		    if (--ac == 0) goto help;
		    ap = *++av;
		}
		if (strlen (ap) != 2)
		    Msg (0, "Two characters are required with -e option.");
		Esc = ap[0];
		MetaEsc = ap[1];
		break;
	    default:
	    help:
		Msg (0, "Use: %s [-a] [-f] [-n] [-exy] [cmd args]", myname);
	    }
	} else break;
    }
    if (nflag && fflag)
	Msg (0, "-f and -n are conflicting options.");
    if ((ShellProg = getenv ("SHELL")) == 0)
	ShellProg = DefaultShell;
    ShellArgs[0] = ShellProg;
    if (ac == 0) {
	ac = 1;
	av = ShellArgs;
    }
    if (GetSockName ()) {
	/* Client */
	s = MakeClientSocket ();
	SendCreateMsg (s, ac, av, aflag);
	close (s);
	exit (0);
    }
    (void) gethostname (HostName, MAXSTR);
    HostName[MAXSTR-1] = '\0';
    if (ap = index (HostName, '.'))
	*ap = '\0';
    s = MakeServerSocket ();
    InitTerm ();
    if (fflag)
	flowctl = 1;
    else if (nflag)
	flowctl = 0;
    MakeNewEnv ();
    GetTTY (0, &OldMode);
    ospeed = (short)OldMode.m_ttyb.sg_ospeed;
    InitUtmp ();
#ifdef LOADAV
    InitKmem ();
#endif
    signal (SIGHUP, Finit);
    signal (SIGINT, Finit);
    signal (SIGQUIT, Finit);
    signal (SIGTERM, Finit);
    InitKeytab ();
    sprintf (rc, "%.*s/.screenrc", 245, home);
    ReadRc (rc);
    if ((n = MakeWindow (*av, av, aflag, 0, (char *)0)) == -1) {
	SetTTY (0, &OldMode);
	FinitTerm ();
	exit (1);
    }
    SetCurrWindow (n);
    HasWindow = 1;
    SetMode (&OldMode, &NewMode);
    SetTTY (0, &NewMode);
    signal (SIGCHLD, SigChld);
    tv.tv_usec = 0;
    while (1) {
	if (status) {
	    time (&now);
	    if (now - TimeDisplayed < MSGWAIT) {
		tv.tv_sec = MSGWAIT - (now - TimeDisplayed);
	    } else RemoveStatus (curr);
	}
	r = 0;
	w = 0;
	if (inlen)
	    w |= 1 << curr->ptyfd;
	else
	    r |= 1 << 0;
	for (pp = wtab; pp < wtab+MAXWIN; ++pp) {
	    if (!(p = *pp))
		continue;
	    if ((*pp)->active && status)
		continue;
	    if ((*pp)->outlen > 0)
		continue;
	    r |= 1 << (*pp)->ptyfd;
	}
	r |= 1 << s;
	fflush (stdout);
	if (select (32, &r, &w, &x, status ? &tv : (struct timeval *)0) == -1) {
	    if (errno == EINTR)
		continue;
	    HasWindow = 0;
	    Msg (errno, "select");
	    /*NOTREACHED*/
	}
	if (GotSignal && !status) {
	    SigHandler ();
	    continue;
	}
	if (r & 1 << s) {
	    RemoveStatus (curr);
	    ReceiveMsg (s);
	}
	if (r & 1 << 0) {
	    RemoveStatus (curr);
	    if (ESCseen) {
		inbuf[0] = Esc;
		inlen = read (0, inbuf+1, IOSIZE-1) + 1;
		ESCseen = 0;
	    } else {
		inlen = read (0, inbuf, IOSIZE);
	    }
	    if (inlen > 0)
		inlen = ProcessInput (inbuf, inlen);
	    if (inlen > 0)
		continue;
	}
	if (GotSignal && !status) {
	    SigHandler ();
	    continue;
	}
	if (w & 1 << curr->ptyfd && inlen > 0) {
	    if ((len = write (curr->ptyfd, inbuf, inlen)) > 0) {
		inlen -= len;
		bcopy (inbuf+len, inbuf, inlen);
	    }
	}
	if (GotSignal && !status) {
	    SigHandler ();
	    continue;
	}
	for (pp = wtab; pp < wtab+MAXWIN; ++pp) {
	    if (!(p = *pp))
		continue;
	    if (p->outlen) {
		WriteString (p, p->outbuf, p->outlen);
	    } else if (r & 1 << p->ptyfd) {
		if ((len = read (p->ptyfd, buf, IOSIZE)) == -1) {
		    if (errno == EWOULDBLOCK)
			len = 0;
		}
		if (len > 0)
		    WriteString (p, buf, len);
	    }
	    if (p->bell) {
		p->bell = 0;
		Msg (0, MakeBellMsg (pp-wtab));
	    }
	}
	if (GotSignal && !status)
	    SigHandler ();
    }
    /*NOTREACHED*/
}

static SigHandler () {
    while (GotSignal) {
	GotSignal = 0;
	DoWait ();
    }
}

static SigChld () {
    GotSignal = 1;
}

static DoWait () {
    register pid;
    register struct win **pp;
    union wait wstat;

    while ((pid = wait3 (&wstat, WNOHANG|WUNTRACED, NULL)) > 0) {
	for (pp = wtab; pp < wtab+MAXWIN; ++pp) {
	    if (*pp && pid == (*pp)->wpid) {
		if (WIFSTOPPED (wstat)) {
		    kill((*pp)->wpid, SIGCONT);
		} else {
		    if (*pp == curr)
			curr = 0;
		    if (*pp == other)
			other = 0;
		    FreeWindow (*pp);
		    *pp = 0;
		}
	    }
	}
    }
    CheckWindows ();
}

static CheckWindows () {
    register struct win **pp;

    /* If the current window disappeared and the "other" window is still
     * there, switch to the "other" window, else switch to the window
     * with the lowest index.
     * If there current window is still there, but the "other" window
     * vanished, "SetCurrWindow" is called in order to assign a new value
     * to "other".
     * If no window is alive at all, exit.
     */
    if (!curr && other) {
	SwitchWindow (OtherNum);
	return;
    }
    if (curr && !other) {
	SetCurrWindow (CurrNum);
	return;
    }
    for (pp = wtab; pp < wtab+MAXWIN; ++pp) {
	if (*pp) {
	    if (!curr)
		SwitchWindow (pp-wtab);
	    return;
	}
    }
    Finit ();
}

static Finit () {
    register struct win *p, **pp;

    for (pp = wtab; pp < wtab+MAXWIN; ++pp) {
	if (p = *pp)
	    FreeWindow (p);
    }
    SetTTY (0, &OldMode);
    FinitTerm ();
    printf ("[screen is terminating]\n");
    exit (0);
}

static InitKeytab () {
    register i;

    ktab['h'].type = ktab[Ctrl('h')].type = KEY_HARDCOPY;
    ktab['z'].type = ktab[Ctrl('z')].type = KEY_SUSPEND;
    ktab['c'].type = ktab[Ctrl('c')].type = KEY_SHELL;
    ktab[' '].type = ktab[Ctrl(' ')].type = 
    ktab['n'].type = ktab[Ctrl('n')].type = KEY_NEXT;
    ktab['-'].type = ktab['p'].type = ktab[Ctrl('p')].type = KEY_PREV;
    ktab['k'].type = ktab[Ctrl('k')].type = KEY_KILL;
    ktab['l'].type = ktab[Ctrl('l')].type = KEY_REDISPLAY;
    ktab['w'].type = ktab[Ctrl('w')].type = KEY_WINDOWS;
    ktab['v'].type = ktab[Ctrl('v')].type = KEY_VERSION;
    ktab['q'].type = ktab[Ctrl('q')].type = KEY_XON;
    ktab['s'].type = ktab[Ctrl('s')].type = KEY_XOFF;
    ktab['t'].type = ktab[Ctrl('t')].type = KEY_INFO;
    ktab['.'].type = KEY_TERMCAP;
    ktab[Ctrl('\\')].type = KEY_QUIT;
    ktab[Esc].type = KEY_OTHER;
    for (i = 0; i <= 9; i++)
	ktab[i+'0'].type = KEY_0+i;
}

static ProcessInput (buf, len) char *buf; {
    register n, k;
    register char *s, *p;
    register struct win **pp;

    for (s = p = buf; len > 0; len--, s++) {
	if (*s == Esc) {
	    if (len > 1) {
		len--; s++;
		k = ktab[*s].type;
		if (*s == MetaEsc) {
		    *p++ = Esc;
		} else if (k >= KEY_0 && k <= KEY_9) {
		    p = buf;
		    SwitchWindow (k - KEY_0);
		} else switch (ktab[*s].type) {
		case KEY_TERMCAP:
		    p = buf;
		    WriteFile (0);
		    break;
		case KEY_HARDCOPY:
		    p = buf;
		    WriteFile (1);
		    break;
		case KEY_SUSPEND:
		    p = buf;
		    SetTTY (0, &OldMode);
		    FinitTerm ();
		    kill (getpid (), SIGTSTP);
		    SetTTY (0, &NewMode);
		    Activate (wtab[CurrNum]);
		    break;
		case KEY_SHELL:
		    p = buf;
		    if ((n = MakeWindow (ShellProg, ShellArgs,
			    0, 0, (char *)0)) != -1)
			SwitchWindow (n);
		    break;
		case KEY_NEXT:
		    p = buf;
		    if (MoreWindows ())
			SwitchWindow (NextWindow ());
		    break;
		case KEY_PREV:
		    p = buf;
		    if (MoreWindows ())
			SwitchWindow (PreviousWindow ());
		    break;
		case KEY_KILL:
		    p = buf;
		    FreeWindow (wtab[CurrNum]);
		    if (other == curr)
			other = 0;
		    curr = wtab[CurrNum] = 0;
		    CheckWindows ();
		    break;
		case KEY_QUIT:
		    for (pp = wtab; pp < wtab+MAXWIN; ++pp)
			if (*pp) FreeWindow (*pp);
		    Finit ();
		    /*NOTREACHED*/
		case KEY_REDISPLAY:
		    p = buf;
		    Activate (wtab[CurrNum]);
		    break;
		case KEY_WINDOWS:
		    p = buf;
		    ShowWindows ();
		    break;
		case KEY_VERSION:
		    p = buf;
		    Msg (0, "%s  %s", ScreenVersion, AnsiVersion);
		    break;
		case KEY_INFO:
		    p = buf;
		    ShowInfo ();
		    break;
		case KEY_OTHER:
		    p = buf;
		    if (MoreWindows ())
			SwitchWindow (OtherNum);
		    break;
		case KEY_XON:
		    *p++ = Ctrl('q');
		    break;
		case KEY_XOFF:
		    *p++ = Ctrl('s');
		    break;
		case KEY_CREATE:
		    p = buf;
		    if ((n = MakeWindow (ktab[*s].args[0], ktab[*s].args,
			    0, 0, (char *)0)) != -1)
			SwitchWindow (n);
		    break;
		}
	    } else ESCseen = 1;
	} else *p++ = *s;
    }
    return p - buf;
}

static SwitchWindow (n) {
    if (!wtab[n])
	return;
    SetCurrWindow (n);
    Activate (wtab[n]);
}

static SetCurrWindow (n) {
    /*
     * If we come from another window, this window becomes the
     * "other" window:
     */
    if (curr) {
	curr->active = 0;
	other = curr;
	OtherNum = CurrNum;
    }
    CurrNum = n;
    curr = wtab[n];
    curr->active = 1;
    /*
     * If the "other" window is currently undefined (at program start
     * or because it has died), or if the "other" window is equal to the
     * one just selected, we try to find a new one:
     */
    if (other == 0 || other == curr) {
	OtherNum = NextWindow ();
	other = wtab[OtherNum];
    }
}

static NextWindow () {
    register struct win **pp;

    for (pp = wtab+CurrNum+1; pp != wtab+CurrNum; ++pp) {
	if (pp == wtab+MAXWIN)
	    pp = wtab;
	if (*pp)
	    break;
    }
    return pp-wtab;
}

static PreviousWindow () {
    register struct win **pp;

    for (pp = wtab+CurrNum-1; pp != wtab+CurrNum; --pp) {
	if (pp < wtab)
	    pp = wtab+MAXWIN-1;
	if (*pp)
	    break;
    }
    return pp-wtab;
}

static MoreWindows () {
    register struct win **pp;
    register n;

    for (n = 0, pp = wtab; pp < wtab+MAXWIN; ++pp)
	if (*pp) ++n;
    if (n <= 1)
	Msg (0, "No other window.");
    return n > 1;
}

static FreeWindow (wp) struct win *wp; {
    register i;

    RemoveUtmp (wp->slot);
    (void) chmod (wp->tty, 0666);
    (void) chown (wp->tty, 0, 0);
    close (wp->ptyfd);
    for (i = 0; i < rows; ++i) {
	free (wp->image[i]);
	free (wp->attr[i]);
    }
    free (wp->image);
    free (wp->attr);
    free (wp);
}

static MakeWindow (prog, args, aflag, StartAt, dir)
	char *prog, **args, *dir; {
    register struct win **pp, *p;
    register char **cp;
    register n, f;
    int tf;
    int mypid;
    char ebuf[10];

    pp = wtab+StartAt;
    do {
	if (*pp == 0)
	    break;
	if (++pp == wtab+MAXWIN)
	    pp = wtab;
    } while (pp != wtab+StartAt);
    if (*pp) {
	Msg (0, "No more windows.");
	return -1;
    }
    n = pp - wtab;
    if ((f = OpenPTY ()) == -1) {
	Msg (0, "No more PTYs.");
	return -1;
    }
    fcntl (f, F_SETFL, FNDELAY);
    if ((p = *pp = (struct win *)malloc (sizeof (struct win))) == 0) {
nomem:
	Msg (0, "Out of memory.");
	return -1;
    }
    if ((p->image = (char **)malloc (rows * sizeof (char *))) == 0)
	goto nomem;
    for (cp = p->image; cp < p->image+rows; ++cp) {
	if ((*cp = malloc (cols)) == 0)
	    goto nomem;
	bclear (*cp, cols);
    }
    if ((p->attr = (char **)malloc (rows * sizeof (char *))) == 0)
	goto nomem;
    for (cp = p->attr; cp < p->attr+rows; ++cp) {
	if ((*cp = malloc (cols)) == 0)
	    goto nomem;
	bzero (*cp, cols);
    }
    if ((p->tabs = malloc (cols+1)) == 0)  /* +1 because 0 <= x <= cols */
	goto nomem;
    ResetScreen (p);
    p->aflag = aflag;
    p->active = 0;
    p->bell = 0;
    p->outlen = 0;
    p->ptyfd = f;
    strncpy (p->cmd, Filename (args[0]), MAXSTR-1);
    p->cmd[MAXSTR-1] = '\0';
    strncpy (p->tty, TtyName, MAXSTR-1);
    (void) chown (TtyName, getuid (), getgid ());
    (void) chmod (TtyName, 0622);
    p->slot = SetUtmp (TtyName);
    switch (p->wpid = fork ()) {
    case -1:
	Msg (errno, "Cannot fork");
	free (p);
	return -1;
    case 0:
	signal (SIGHUP, SIG_DFL);
	signal (SIGINT, SIG_DFL);
	signal (SIGQUIT, SIG_DFL);
	signal (SIGTERM, SIG_DFL);
	setuid (getuid ());
	setgid (getgid ());
	if (dir && chdir (dir) == -1) {
	    SendErrorMsg ("Cannot chdir to %s: %s", dir, sys_errlist[errno]);
	    exit (1);
	}
	mypid = getpid ();
	if ((f = open ("/dev/tty", O_RDWR)) != -1) {
	    ioctl (f, TIOCNOTTY, (char *)0);
	    close (f);
	}
	if ((tf = open (TtyName, O_RDWR)) == -1) {
	    SendErrorMsg ("Cannot open %s: %s", TtyName, sys_errlist[errno]);
	    exit (1);
	}
	dup2 (tf, 0);
	dup2 (tf, 1);
	dup2 (tf, 2);
	for (f = getdtablesize () - 1; f > 2; f--)
	    close (f);
	ioctl (0, TIOCSPGRP, &mypid);
	setpgrp (0, mypid);
	SetTTY (0, &OldMode);
	NewEnv[2] = MakeTermcap (aflag);
	sprintf (ebuf, "WINDOW=%d", n);
	NewEnv[3] = ebuf;
	execvpe (prog, args, NewEnv);
	SendErrorMsg ("Cannot exec %s: %s", prog, sys_errlist[errno]);
	exit (1);
    }
    return n;
}

execvpe (prog, args, env) char *prog, **args, **env; {
    register char *path, *p;
    char buf[1024];
    char *shargs[MAXARGS+1];
    register i, eaccess = 0;

    if (prog[0] == '/')
	path = "";
    else if ((path = getenv ("PATH")) == 0)
	path = DefaultPath;
    do {
	p = buf;
	while (*path && *path != ':')
	    *p++ = *path++;
	if (p > buf)
	    *p++ = '/';
	strcpy (p, prog);
	if (*path)
	    ++path;
	execve (buf, args, env);
	switch (errno) {
	case ENOEXEC:
	    shargs[0] = DefaultShell;
	    shargs[1] = buf;
	    for (i = 1; shargs[i+1] = args[i]; ++i)
		;
	    execve (DefaultShell, shargs, env);
	    return;
	case EACCES:
	    eaccess = 1;
	    break;
	case ENOMEM: case E2BIG: case ETXTBSY:
	    return;
	}
    } while (*path);
    if (eaccess)
	errno = EACCES;
}

static WriteFile (dump) {   /* dump==0: create .termcap, dump==1: hardcopy */
    register i, j, k;
    register char *p;
    register FILE *f;
    char fn[1024];
    int pid, s;

    if (dump)
	sprintf (fn, "hardcopy.%d", CurrNum);
    else
	sprintf (fn, "%s/%s/.termcap", home, SockDir);
    switch (pid = fork ()) {
    case -1:
	Msg (errno, "fork");
	return;
    case 0:
	setuid (getuid ());
	setgid (getgid ());
	if ((f = fopen (fn, "w")) == NULL)
	    exit (1);
	if (dump) {
	    for (i = 0; i < rows; ++i) {
		p = curr->image[i];
		for (k = cols-1; k >= 0 && p[k] == ' '; --k) ;
		for (j = 0; j <= k; ++j)
		    putc (p[j], f);
		putc ('\n', f);
	    }
	} else {
	    if (p = index (MakeTermcap (curr->aflag), '=')) {
		fputs (++p, f);
		putc ('\n', f);
	    }
	}
	fclose (f);
	exit (0);
    default:
	while ((i = wait (&s)) != pid)
	    if (i == -1) return;
	if ((s >> 8) & 0377)
	    Msg (0, "Cannot open \"%s\".", fn);
	else
	    Msg (0, "%s written to \"%s\".", dump ? "Screen image" :
		"Termcap entry", fn);
    }
}

static ShowWindows () {
    char buf[1024];
    register char *s;
    register struct win **pp, *p;

    for (s = buf, pp = wtab; pp < wtab+MAXWIN; ++pp) {
	if ((p = *pp) == 0)
	    continue;
	if (s - buf + 5 + strlen (p->cmd) > cols-1)
	    break;
	if (s > buf) {
	    *s++ = ' '; *s++ = ' ';
	}
	*s++ = pp - wtab + '0';
	if (p == curr)
	    *s++ = '*';
	else if (p == other)
	    *s++ = '-';
	*s++ = ' ';
	strcpy (s, p->cmd);
	s += strlen (s);
    }
    Msg (0, buf);
}

static ShowInfo () {
    char buf[1024], *p;
    register struct win *wp = curr;
    struct tm *tp;
    time_t now;
#ifdef LOADAV
    double av[3];
#endif

    time (&now);
    tp = localtime (&now);
    sprintf (buf, "%2d:%02.2d:%02.2d %s", tp->tm_hour, tp->tm_min, tp->tm_sec,
	HostName);
#ifdef LOADAV
    if (avenrun && GetAvenrun (av)) {
	p = buf + strlen (buf);
	sprintf (p, " %2.2f %2.2f %2.2f", av[0], av[1], av[2]);
    }
#endif
    p = buf + strlen (buf);
    sprintf (p, " (%d,%d) %cflow %cins %corg %cwrap %cpad", wp->y, wp->x,
	flowctl ? '+' : '-',
	wp->insert ? '+' : '-', wp->origin ? '+' : '-',
	wp->wrap ? '+' : '-', wp->keypad ? '+' : '-');
    Msg (0, buf);
}

static OpenPTY () {
    register char *p, *l, *d;
    register i, f, tf;

    strcpy (PtyName, PtyProto);
    strcpy (TtyName, TtyProto);
    for (p = PtyName, i = 0; *p != 'X'; ++p, ++i) ;
    for (l = "pqr"; *p = *l; ++l) {
	for (d = "0123456789abcdef"; p[1] = *d; ++d) {
	    if ((f = open (PtyName, O_RDWR)) != -1) {
		TtyName[i] = p[0];
		TtyName[i+1] = p[1];
		if ((tf = open (TtyName, O_RDWR)) != -1) {
		    close (tf);
		    return f;
		}
		close (f);
	    }
	}
    }
    return -1;
}

static SetTTY (fd, mp) struct mode *mp; {
    ioctl (fd, TIOCSETP, &mp->m_ttyb);
    ioctl (fd, TIOCSETC, &mp->m_tchars);
    ioctl (fd, TIOCSLTC, &mp->m_ltchars);
    ioctl (fd, TIOCLSET, &mp->m_lmode);
    ioctl (fd, TIOCSETD, &mp->m_ldisc);
}

static GetTTY (fd, mp) struct mode *mp; {
    ioctl (fd, TIOCGETP, &mp->m_ttyb);
    ioctl (fd, TIOCGETC, &mp->m_tchars);
    ioctl (fd, TIOCGLTC, &mp->m_ltchars);
    ioctl (fd, TIOCLGET, &mp->m_lmode);
    ioctl (fd, TIOCGETD, &mp->m_ldisc);
}

static SetMode (op, np) struct mode *op, *np; {
    *np = *op;
    np->m_ttyb.sg_flags &= ~(CRMOD|ECHO);
    np->m_ttyb.sg_flags |= CBREAK;
    np->m_tchars.t_intrc = -1;
    np->m_tchars.t_quitc = -1;
    if (!flowctl) {
	np->m_tchars.t_startc = -1;
	np->m_tchars.t_stopc = -1;
    }
    np->m_ltchars.t_suspc = -1;
    np->m_ltchars.t_dsuspc = -1;
    np->m_ltchars.t_flushc = -1;
    np->m_ltchars.t_lnextc = -1;
}

static GetSockName () {
    struct stat s;
    register client;
    register char *p;

    if (!mflag && (SockName = getenv ("STY")) != 0 && *SockName != '\0') {
	client = 1;
	setuid (getuid ());
	setgid (getgid ());
    } else {
	if ((p = ttyname (0)) == 0 || (p = ttyname (1)) == 0 ||
		(p = ttyname (2)) == 0 || *p == '\0')
	    Msg (0, "screen must run on a tty.");
	SockName = Filename (p);
	client = 0;
    }
    if ((home = getenv ("HOME")) == 0)
	Msg (0, "$HOME is undefined.");
    sprintf (SockPath, "%s/%s", home, SockDir);
    if (stat (SockPath, &s) == -1) {
	if (errno == ENOENT) {
	    if (mkdir (SockPath, 0700) == -1)
		Msg (errno, "Cannot make directory %s", SockPath);
	    (void) chown (SockPath, getuid (), getgid ());
	} else Msg (errno, "Cannot get status of %s", SockPath);
    } else {
	if ((s.st_mode & S_IFMT) != S_IFDIR)
	    Msg (0, "%s is not a directory.", SockPath);
	if ((s.st_mode & 0777) != 0700)
	    Msg (0, "Directory %s must have mode 700.", SockPath);
	if (s.st_uid != getuid ())
	    Msg (0, "You are not the owner of %s.", SockPath);
    }
    strcat (SockPath, "/");
    strcat (SockPath, SockName);
    return client;
}

static MakeServerSocket () {
    register s;
    struct sockaddr_un a;

    (void) unlink (SockPath);
    if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)
	Msg (errno, "socket");
    a.sun_family = AF_UNIX;
    strcpy (a.sun_path, SockPath);
    if (bind (s, (struct sockaddr *)&a, strlen (SockPath)+2) == -1)
	Msg (errno, "bind");
    (void) chown (SockPath, getuid (), getgid ());
    if (listen (s, 5) == -1)
	Msg (errno, "listen");
    return s;
}

static MakeClientSocket () {
    register s;
    struct sockaddr_un a;

    if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)
	Msg (errno, "socket");
    a.sun_family = AF_UNIX;
    strcpy (a.sun_path, SockPath);
    if (connect (s, (struct sockaddr *)&a, strlen (SockPath)+2) == -1)
	Msg (errno, "connect: %s", SockPath);
    return s;
}

static SendCreateMsg (s, ac, av, aflag) char **av; {
    struct msg m;
    register char *p;
    register len, n;

    m.type = MSG_CREATE;
    p = m.m.create.line;
    for (n = 0; ac > 0 && n < MAXARGS-1; ++av, --ac, ++n) {
	len = strlen (*av) + 1;
	if (p + len >= m.m.create.line+MAXLINE)
	    break;
	strcpy (p, *av);
	p += len;
    }
    m.m.create.nargs = n;
    m.m.create.aflag = aflag;
    if (getwd (m.m.create.dir) == 0)
	Msg (0, "%s", m.m.create.dir);
    if (write (s, &m, sizeof (m)) != sizeof (m))
	Msg (errno, "write");
}

/*VARARGS1*/
static SendErrorMsg (fmt, p1, p2, p3, p4, p5, p6) char *fmt; {
    register s;
    struct msg m;

    s = MakeClientSocket ();
    m.type = MSG_ERROR;
    sprintf (m.m.message, fmt, p1, p2, p3, p4, p5, p6);
    (void) write (s, &m, sizeof (m));
    close (s);
    sleep (2);
}

static ReceiveMsg (s) {
    register ns;
    struct sockaddr_un a;
    int len = sizeof (a);
    struct msg m;

    if ((ns = accept (s, (struct sockaddr *)&a, &len)) == -1) {
	Msg (errno, "accept");
	return;
    }
    if ((len = read (ns, &m, sizeof (m))) != sizeof (m)) {
	if (len == -1)
	    Msg (errno, "read");
	else
	    Msg (0, "Short message (%d bytes)", len);
	close (ns);
	return;
    }
    switch (m.type) {
    case MSG_CREATE:
	ExecCreate (&m);
	break;
    case MSG_ERROR:
	Msg (0, "%s", m.m.message);
	break;
    default:
	Msg (0, "Invalid message (type %d).", m.type);
    }
    close (ns);
}

static ExecCreate (mp) struct msg *mp; {
    char *args[MAXARGS];
    register n;
    register char **pp = args, *p = mp->m.create.line;

    for (n = mp->m.create.nargs; n > 0; --n) {
	*pp++ = p;
	p += strlen (p) + 1;
    }
    *pp = 0;
    if ((n = MakeWindow (mp->m.create.line, args, mp->m.create.aflag, 0,
	    mp->m.create.dir)) != -1)
	SwitchWindow (n);
}

static ReadRc (fn) char *fn; {
    FILE *f;
    register char *p, **pp, **ap;
    register argc, num, c;
    char buf[256];
    char *args[MAXARGS];
    int key;

    ap = args;
    if (access (fn, R_OK) == -1)
	return;
    if ((f = fopen (fn, "r")) == NULL)
	return;
    while (fgets (buf, 256, f) != NULL) {
	if (p = rindex (buf, '\n'))
	    *p = '\0';
	if ((argc = Parse (fn, buf, ap)) == 0)
	    continue;
	if (strcmp (ap[0], "escape") == 0) {
	    p = ap[1];
	    if (argc < 2 || strlen (p) != 2)
		Msg (0, "%s: two characters required after escape.", fn);
	    Esc = *p++;
	    MetaEsc = *p;
	} else if (strcmp (ap[0], "chdir") == 0) {
	    p = argc < 2 ? home : ap[1];
	    if (chdir (p) == -1)
		Msg (errno, "%s", p);
	} else if (strcmp (ap[0], "bell") == 0) {
	    if (argc != 2) {
		Msg (0, "%s: bell: one argument required.", fn);
	    } else {
		if ((BellString = malloc (strlen (ap[1]) + 1)) == 0)
		    Msg (0, "Out of memory.");
		strcpy (BellString, ap[1]);
	    }
	} else if (strcmp (ap[0], "screen") == 0) {
	    num = 0;
	    if (argc > 1 && IsNum (ap[1], 10)) {
		num = atoi (ap[1]);
		if (num < 0 || num > MAXWIN-1)
		    Msg (0, "%s: illegal screen number %d.", fn, num);
		--argc; ++ap;
	    }
	    if (argc < 2) {
		ap[1] = ShellProg; argc = 2;
	    }
	    ap[argc] = 0;
	    (void) MakeWindow (ap[1], ap+1, 0, num, (char *)0);
	} else if (strcmp (ap[0], "bind") == 0) {
	    p = ap[1];
	    if (argc < 2 || *p == '\0')
		Msg (0, "%s: key expected after bind.", fn);
	    if (p[1] == '\0') {
		key = *p;
	    } else if (p[0] == '^' && p[1] != '\0' && p[2] == '\0') {
		c = p[1];
		if (isupper (c))
		    p[1] = tolower (c);    
		key = Ctrl(c);
	    } else if (IsNum (p, 7)) {
		(void) sscanf (p, "%o", &key);
	    } else {
		Msg (0,
		    "%s: bind: character, ^x, or octal number expected.", fn);
	    }
	    if (argc < 3) {
		ktab[key].type = 0;
	    } else {
		for (pp = KeyNames; *pp; ++pp)
		    if (strcmp (ap[2], *pp) == 0) break;
		if (*pp) {
		    ktab[key].type = pp-KeyNames+1;
		} else {
		    ktab[key].type = KEY_CREATE;
		    ktab[key].args = SaveArgs (argc-2, ap+2);
		}
	    }
	} else Msg (0, "%s: unknown keyword \"%s\".", fn, ap[0]);
    }
    fclose (f);
}

static Parse (fn, buf, args) char *fn, *buf, **args; {
    register char *p = buf, **ap = args;
    register delim, argc = 0;

    argc = 0;
    for (;;) {
	while (*p && (*p == ' ' || *p == '\t')) ++p;
	if (*p == '\0' || *p == '#')
	    return argc;
	if (argc > MAXARGS-1)
	    Msg (0, "%s: too many tokens.", fn);
	delim = 0;
	if (*p == '"' || *p == '\'') {
	    delim = *p; *p = '\0'; ++p;
	}
	++argc;
	*ap = p; ++ap;
	while (*p && !(delim ? *p == delim : (*p == ' ' || *p == '\t')))
	    ++p;
	if (*p == '\0') {
	    if (delim)
		Msg (0, "%s: Missing quote.", fn);
	    else
		return argc;
	}
	*p++ = '\0';
    }
}

static char **SaveArgs (argc, argv) register argc; register char **argv; {
    register char **ap, **pp;

    if ((pp = ap = (char **)malloc ((argc+1) * sizeof (char **))) == 0)
	Msg (0, "Out of memory.");
    while (argc--) {
	if ((*pp = malloc (strlen (*argv)+1)) == 0)
	    Msg (0, "Out of memory.");
	strcpy (*pp, *argv);
	++pp; ++argv;
    }
    *pp = 0;
    return ap;
}

static MakeNewEnv () {
    register char **op, **np = NewEnv;
    static char buf[MAXSTR];

    if (strlen (SockName) > MAXSTR-5)
	SockName = "?";
    sprintf (buf, "STY=%s", SockName);
    *np++ = buf;
    *np++ = Term;
    np += 2;
    for (op = environ; *op; ++op) {
	if (np == NewEnv + MAXARGS - 1)
	    break;
	if (!IsSymbol (*op, "TERM") && !IsSymbol (*op, "TERMCAP")
		&& !IsSymbol (*op, "STY"))
	    *np++ = *op;
    }
    *np = 0;
}

static IsSymbol (e, s) register char *e, *s; {
    register char *p;
    register n;

    for (p = e; *p && *p != '='; ++p) ;
    if (*p) {
	*p = '\0';
	n = strcmp (e, s);
	*p = '=';
	return n == 0;
    }
    return 0;
}

/*VARARGS2*/
Msg (err, fmt, p1, p2, p3, p4, p5, p6) char *fmt; {
    char buf[1024];
    register char *p = buf;

    sprintf (p, fmt, p1, p2, p3, p4, p5, p6);
    if (err) {
	p += strlen (p);
	if (err > 0 && err < sys_nerr)
	    sprintf (p, ": %s", sys_errlist[err]);
	else
	    sprintf (p, ": Error %d", err);
    }
    if (HasWindow) {
	MakeStatus (buf, curr);
    } else {
	printf ("%s\r\n", buf);
	exit (1);
    }
}

bclear (p, n) char *p; {
    bcopy (blank, p, n);
}

static char *Filename (s) char *s; {
    register char *p;

    p = s + strlen (s) - 1;
    while (p >= s && *p != '/') --p;
    return ++p;
}

static IsNum (s, base) register char *s; register base; {
    for (base += '0'; *s; ++s)
	if (*s < '0' || *s > base)
	    return 0;
    return 1;
}

static char *MakeBellMsg (n) {
    static char buf[MAXSTR];
    register char *p = buf, *s = BellString;

    for (s = BellString; *s && p < buf+MAXSTR-1; s++)
	*p++ = (*s == '%') ? n + '0' : *s;
    *p = '\0';
    return buf;
}

static InitUtmp () {
    struct passwd *p;

    if ((utmpf = open (UtmpName, O_WRONLY)) == -1) {
	if (errno != EACCES)
	    Msg (errno, UtmpName);
	return;
    }
    if ((LoginName = getlogin ()) == 0 || LoginName[0] == '\0') {
	if ((p = getpwuid (getuid ())) == 0)
	    return;
	LoginName = p->pw_name;
    }
    utmp = 1;
}

static SetUtmp (name) char *name; {
    register char *p;
    register struct ttyent *tp;
    register slot = 1;
    struct utmp u;

    if (!utmp)
	return 0;
    if (p = rindex (name, '/'))
	++p;
    else p = name;
    setttyent ();
    while ((tp = getttyent ()) != NULL && strcmp (p, tp->ty_name) != 0)
	++slot;
    if (tp == NULL)
	return 0;
    strncpy (u.ut_line, p, 8);
    strncpy (u.ut_name, LoginName, 8);
    u.ut_host[0] = '\0';
    time (&u.ut_time);
    (void) lseek (utmpf, (long)(slot * sizeof (u)), 0);
    (void) write (utmpf, &u, sizeof (u));
    return slot;
}

static RemoveUtmp (slot) {
    struct utmp u;

    if (slot) {
	bzero (&u, sizeof (u));
	(void) lseek (utmpf, (long)(slot * sizeof (u)), 0);
	(void) write (utmpf, &u, sizeof (u));
    }
}

#ifndef GETTTYENT

static setttyent () {
    struct stat s;
    register f;
    register char *p, *ep;

    if (ttnext) {
	ttnext = tt;
	return;
    }
    if ((f = open (ttys, O_RDONLY)) == -1 || fstat (f, &s) == -1)
	Msg (errno, ttys);
    if ((tt = malloc (s.st_size + 1)) == 0)
	Msg (0, "Out of memory.");
    if (read (f, tt, s.st_size) != s.st_size)
	Msg (errno, ttys);
    close (f);
    for (p = tt, ep = p + s.st_size; p < ep; ++p)
	if (*p == '\n') *p = '\0';
    *p = '\0';
    ttnext = tt;
}

static struct ttyent *getttyent () {
    static struct ttyent t;

    if (*ttnext == '\0')
	return NULL;
    t.ty_name = ttnext + 2;
    ttnext += strlen (ttnext) + 1;
    return &t;
}

#endif

#ifdef LOADAV

static InitKmem () {
    if ((kmemf = open (KmemName, O_RDONLY)) == -1)
	return;
    nl[0].n_name = AvenrunSym;
    nlist (UnixName, nl);
    if (nl[0].n_type == 0 || nl[0].n_value == 0)
	return;
    avenrun = 1;
}

static GetAvenrun (av) double av[]; {
    if (lseek (kmemf, nl[0].n_value, 0) == -1)
	return 0;
    if (read (kmemf, av, 3*sizeof (double)) != 3*sizeof (double))
	return 0;
    return 1;
}

#endif
SHAR_EOF
if test 32087 -ne "`wc -c < 'screen.c'`"
then
	echo shar: error transmitting "'screen.c'" '(should have been 32087 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0

-- 

Rich $alz			"Anger is an energy"
Cronus Project, BBN Labs	rsalz@bbn.com
Moderator, comp.sources.unix	sources@uunet.uu.net