diff options
author | jnweiger <jnweiger> | 2005-12-16 15:10:17 +0000 |
---|---|---|
committer | jnweiger <jnweiger> | 2005-12-16 15:10:17 +0000 |
commit | 8bc5213d76b3acfbb8d0cddc2c91b7685aab731e (patch) | |
tree | 78a8d136bd8cf3ed5e8c2b4c0198d62218e4482c /src | |
parent | 30f597b9f18d355253b9484bb2f4a1fa5e2e3fb8 (diff) | |
download | screen-8bc5213d76b3acfbb8d0cddc2c91b7685aab731e.tar.gz |
historic version screen-1.1
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 59 | ||||
-rw-r--r-- | src/README | 94 | ||||
-rw-r--r-- | src/ansi.c | 1450 | ||||
-rw-r--r-- | src/screen.1 | 619 | ||||
-rw-r--r-- | src/screen.c | 1655 | ||||
-rw-r--r-- | src/screen.h | 68 |
6 files changed, 3945 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..b77fa3c --- /dev/null +++ b/src/Makefile @@ -0,0 +1,59 @@ +# @(#)Makefile 1.5 88/12/13 22:13:36 +# The following options can be set: +# +# -DLOCK -- you want to have the possibility to lock your terminal +# while you are running screen +# (Please provide a suitable value for "DefaultLock" in +# screen.c - reflecting your sytem responsibilities) +# -DLOADAV -- your system maintains a load average like 4.3 BSD does +# (an array of three doubles called _avenrun; it is read +# from /dev/kmem; _avenrun is taken from the namelist of +# /vmunix). Don't set this on a Sun under SunOS 3.x. +# -DGETTTYENT -- your system has the new format /etc/ttys (like 4.3 BSD) +# and the getttyent(3) library functions. +# -DSUNOS -- If you have SunOS 4.0 or up, you should set this to use +# the kernel virtual memory library, when you want +# to have LOADAV ( set LOADAV also). +# -DSEQUENT -- eliminates bugs in DYNIX and chooses different +# pty naming for the sequent. +# +# You should install as set-uid with owner root, so that it can read/write +# /etc/utmp, read /dev/kmem, and chown/chmod the allocated pseudo-ttys. +SHELL=/bin/sh +CFLAGS= -g + +BINDIR=/usr/local/bin +PART=1 +MANDIR=/usr/local/man/man$(PART) + +OPTIONS=-DLOCK -DSEQUENT -DSEQUENT -DLOADAV +LIBS= -ltermcap + +CFILES= screen.c ansi.c +OFILES= screen.o ansi.o + +screen:$(P) $(OFILES) screen.1 + $(CC) $(CFLAGS) -o screen $(OFILES) $(LIBS) + +screen.o:$(P) screen.c screen.h + $(CC) $(OPTIONS) $(CFLAGS) -c screen.c + +ansi.o:$(P) ansi.c screen.h + $(CC) $(CFLAGS) -c ansi.c + +install: screen screen.1 + install -c -s -o root -g wheel -m 4755 screen $(BINDIR) + install -c -m 444 screen.1 $(MANDIR)/screen.$(PART) + +clean: + rm -f *.o screen + sccs clean + +screen.c: + sccs get screen.c + +screen.h: + sccs get screen.h + +ansi.c: + sccs get ansi.c diff --git a/src/README b/src/README new file mode 100644 index 0000000..a342467 --- /dev/null +++ b/src/README @@ -0,0 +1,94 @@ +"screen" is a window manager that allows you to handle several independent +screens (UNIX ttys) on a single physical terminal; each screen has its own +set of processes connected to it (typically interactive shells). Each +virtual terminal created by "screen" emulates a DEC VT100 plus several ANSI +X3.64 functions (including DEC VT102 features such as line and character +deletion and insertion). + +Since "screen" uses pseudo-ttys, the select system call, and UNIX-domain +sockets, it will not run under a system that does not include these +features of 4.2 and 4.3 BSD UNIX. + +If you want to get a quick idea how "screen" works but don't want to read +the entire manual, do the following: + + - call "screen" without arguments + - wait for the shell prompt; execute some commands + - type ^A ^C (Control-A followed by Control-C) + - wait for the shell prompt; do something in the new window + - type ^A ^A repeatedly to switch between the two windows + - terminate the first shell ("screen" switches to the other window) + - terminate the second shell + +If you have got "vttest" (the VT100 test program from mod.sources) you +may want to run it from within "screen" to verify that it correctly +emulates a VT100 on your terminal (except for 132 column mode and +double width/height characters, of course). + +By the way, "screen" can be used to compensate for certain bugs of "real" +VT100 terminals. For instance, our 4.2 BSD version of mille(6) garbles +the display on terminals of the VT100 family, but it works quite fine +when it is invoked from within "screen". In addition, "screen" enables +you to use EMACS on terminals that are unable to generate Control-S and +Control-Q from the keyboard or that require flow control using Control-S +and Control-Q. This is the reason why I have an alias like + alias emacs "screen emacs" +in my .cshrc file. + + +I have published a beta-test release of screen in the non-moderated +sources newsgroup four months ago. Since then I have received numerous +suggestions for enhancements and improvements, many of which I have +included in this release of "screen". One person reported that screen +does not work correctly on the Ann Arbor Guru-XL; I have not been able +to track the problem down without having more detailed information than +just the termcap entry. + +Major changes between this and the beta-test release are: + + - "screen" now creates an entry in /etc/utmp for each virtual + terminal + - the owner of the tty file for a virtual terminal is set properly + (provided that "screen" is set-uid with owner root) + - the -c option has been eliminated; you can now start a command + in a new window with "screen [cmd [args]]" + - a (user-settable) notification can be displayed in the current + window's message line when the bell is rung in a background window + - a "chdir" command can be placed into ".screenrc" to facilitate + creation of windows in specific directories + - flow control can be disabled by means of a command line option or + a special termcap symbol (necessary for EMACS) + - "C-a s" and "C-a q" can be used to send a Control-S or a Control-Q, + respectively (for certain terminals) + - it is no longer necessary to specify the full pathname when a + command is started in a new window (and with the "bind" command) + - "C-a ." can be used to write the current termcap entry to a file + (useful for "rlogin" which does not propagate TERMCAP) + - "C-a C-t" displays status information (e.g. the load average and + the virtual terminal's parameters) in the message line + - "C-a C-\" closes all windows and terminates screen + - "C-a x" calls a lock program intended to lock the terminal. + Screen wait for this program to terminate before proceeding. + This is available by the compiler option -DLOCK + +Before typing "make", you should have a look into the Makefile. +If your system maintains a 4.3-BSD-style load average, add -DLOADAV to +the compiler options. In addition, you must set -DGETTTYENT if your +system has the new format /etc/ttys and the getttyent(3) routines. + +"screen" should be granted read and write access to /etc/utmp and, if +-DLOADAV has been specified, read access to /vmunix and /dev/kmem. +"screen" should be installed with set-uid and owner root to enable it +to correctly change the owner of newly allocated virtual terminals. +Failing to do this (e.g. if you fear a trojan horse) doesn't have any +major disadvantages, except that w(1) and some other utilities may have +some problems with the tty files of your virtual terminals. + + +Have fun, + Oliver Laumann + Technical University of Berlin, + Communications and Operating Systems Research Group. + + net@tub.BITNET US: pyramid!tub!net Europe: unido!tub!net + diff --git a/src/ansi.c b/src/ansi.c new file mode 100644 index 0000000..dc278f1 --- /dev/null +++ b/src/ansi.c @@ -0,0 +1,1450 @@ +/* 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. + */ + +#ifndef NO_SCCS_ID +static char sccs_id[]="@(#)ansi.c 1.3 88/07/20 01:08:40"; +#endif /* NO_SCCS_ID */ + +char AnsiVersion[] = "ansi 1.0g 1.3 88/07/20"; + +#include <stdio.h> +#include <sys/types.h> +#include "screen.h" + +#define A_SO 0x1 /* Standout mode */ +#define A_US 0x2 /* Underscore mode */ +#define A_BL 0x4 /* Blinking */ +#define A_BD 0x8 /* Bold mode */ +#define A_DI 0x10 /* Dim mode */ +#define A_RV 0x20 /* Reverse mode */ +#define A_MAX 0x20 + +/* Types of movement used by Goto() */ +enum move_t { + M_NONE, + M_UP, + M_DO, + M_LE, + M_RI, + M_RW, + M_CR, +}; + +#define MAXARGS 64 +#define EXPENSIVE 1000 + +extern char *getenv(), *tgetstr(), *tgoto(), *malloc(); + +int rows, cols; +int status; +int flowctl; +char Term[] = "TERM=screen"; +char Termcap[1024]; +char *blank; +char PC; +time_t TimeDisplayed; + +static char tbuf[1024], tentry[1024]; +static char *tp = tentry; +static char *TI, *TE, *BL, *VB, *BC, *CR, *NL, *CL, *IS, *CM; +static char *US, *UE, *SO, *SE, *CE, *CD, *DO, *SR, *SF, *AL; +static char *CS, *DL, *DC, *IC, *IM, *EI, *UP, *ND, *KS, *KE; +static char *MB, *MD, *MH, *MR, *ME; +static AM; +static args[MAXARGS]; +static char GotArg[MAXARGS]; +static NumArgs; +static char GlobalAttr, TmpAttr; +static char *OldImage, *OldAttr; +static last_x, last_y; +static struct win *curr; +static display = 1; +static StrCost; +static UPcost, DOcost, LEcost, NDcost, CRcost, IMcost, EIcost; +static tcLineLen = 100; +static char *null; +static StatLen; +static insert; +static keypad; + +static char *KeyCaps[] = { + "k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7", "k8", "k9", + "kb", "kd", "kh", "kl", "ko", "kr", "ku", + 0 +}; + +static char TermcapConst[] = "TERMCAP=\ +SC|screen|VT 100/ANSI X3.64 virtual terminal|\\\n\ +\t:DO=\\E[%dB:LE=\\E[%dD:RI=\\E[%dC:UP=\\E[%dA:bs:bt=\\E[Z:\\\n\ +\t:cd=\\E[J:ce=\\E[K:cl=\\E[2J\\E[H:cm=\\E[%i%d;%dH:ct=\\E[3g:\\\n\ +\t:do=\\E[B:nd=\\E[C:pt:rc=\\E8:rs=\\Ec:sc=\\E7:st=\\EH:up=\\E[A:"; + +InitTerm () { + register char *s; + + if ((s = getenv ("TERM")) == 0) + Msg (0, "No TERM in environment."); + if (tgetent (tbuf, s) != 1) + Msg (0, "Cannot find termcap entry for %s.", s); + cols = tgetnum ("co"); + rows = tgetnum ("li"); + if (cols <= 0) + cols = 80; + if (rows <= 0) + rows = 24; + if (tgetflag ("hc")) + Msg (0, "You can't run screen on a hardcopy terminal."); + if (tgetflag ("os")) + Msg (0, "You can't run screen on a terminal that overstrikes."); + if (tgetflag ("ns")) + Msg (0, "Terminal must support scrolling."); + if (!(CL = tgetstr ("cl", &tp))) + Msg (0, "Clear screen capability required."); + if (!(CM = tgetstr ("cm", &tp))) + Msg (0, "Addressable cursor capability required."); + if (s = tgetstr ("ps", &tp)) + PC = s[0]; + flowctl = !tgetflag ("NF"); + AM = tgetflag ("am"); + if (tgetflag ("LP")) + AM = 0; + TI = tgetstr ("ti", &tp); + TE = tgetstr ("te", &tp); + if (!(BL = tgetstr ("bl", &tp))) + BL = "\007"; + VB = tgetstr ("vb", &tp); + if (!(BC = tgetstr ("bc", &tp))) { + if (tgetflag ("bs")) + BC = "\b"; + else + BC = tgetstr ("le", &tp); + } + if (!(CR = tgetstr ("cr", &tp))) + CR = "\r"; + if (!(NL = tgetstr ("nl", &tp))) + NL = "\n"; + IS = tgetstr ("is", &tp); + if (tgetnum ("sg") <= 0) { + US = tgetstr ("us", &tp); + UE = tgetstr ("ue", &tp); + SO = tgetstr ("so", &tp); + SE = tgetstr ("se", &tp); + MB = tgetstr ("mb", &tp); + MD = tgetstr ("md", &tp); + MH = tgetstr ("mh", &tp); + MR = tgetstr ("mr", &tp); + ME = tgetstr ("me", &tp); + /* + * Does ME also reverse the effect of SO and/or US? This is not + * clearly specified by the termcap manual. + * Anyway, we should at least look whether ME and SE/UE are equal: + */ + if (SE && UE && ME && (strcmp (SE, UE) == 0 || strcmp (ME, UE) == 0)) + UE = 0; + if (SE && ME && strcmp (SE, ME) == 0) + SE = 0; + } + CE = tgetstr ("ce", &tp); + CD = tgetstr ("cd", &tp); + if (!(DO = tgetstr ("do", &tp))) + DO = NL; + UP = tgetstr ("up", &tp); + ND = tgetstr ("nd", &tp); + SR = tgetstr ("sr", &tp); + if (!(SF = tgetstr ("sf", &tp))) + SF = NL; + AL = tgetstr ("al", &tp); + DL = tgetstr ("dl", &tp); + CS = tgetstr ("cs", &tp); + DC = tgetstr ("dc", &tp); + IC = tgetstr ("ic", &tp); + IM = tgetstr ("im", &tp); + EI = tgetstr ("ei", &tp); + if (tgetflag ("in")) + IC = IM = 0; + if (IC && IC[0] == '\0') + IC = 0; + if (IM && IM[0] == '\0') + IM = 0; + if (EI && EI[0] == '\0') + EI = 0; + KS = tgetstr ("ks", &tp); + KE = tgetstr ("ke", &tp); + blank = malloc (cols); + null = malloc (cols); + OldImage = malloc (cols); + OldAttr = malloc (cols); + if (!(blank && null && OldImage && OldAttr)) + Msg (0, "Out of memory."); + MakeBlankLine (blank, cols); + bzero (null, cols); + UPcost = CalcCost (UP); + DOcost = CalcCost (DO); + LEcost = CalcCost (BC); + NDcost = CalcCost (ND); + CRcost = CalcCost (CR); + IMcost = CalcCost (IM); + EIcost = CalcCost (EI); + PutStr (IS); + PutStr (TI); + PutStr (CL); +} + +FinitTerm () { + PutStr (TE); + PutStr (IS); +} + +static AddCap (s) char *s; { + register n; + + if (tcLineLen + (n = strlen (s)) > 55) { + strcat (Termcap, "\\\n\t:"); + tcLineLen = 0; + } + strcat (Termcap, s); + tcLineLen += n; +} + +char *MakeTermcap (aflag) { + char buf[1024]; + register char **pp, *p; + + strcpy (Termcap, TermcapConst); + sprintf (buf, "li#%d:co#%d:", rows, cols); + AddCap (buf); + if (VB) + AddCap ("vb=\\E[?5h\\E[?5l:"); + if (US) { + AddCap ("us=\\E[4m:"); + AddCap ("ue=\\E[24m:"); + } + if (SO) { + AddCap ("so=\\E[3m:"); + AddCap ("se=\\E[23m:"); + } + if (MB) + AddCap ("mb=\\E[5m:"); + if (MD) + AddCap ("md=\\E[1m:"); + if (MH) + AddCap ("mh=\\E[2m:"); + if (MR) + AddCap ("mr=\\E[7m:"); + if (MB || MD || MH || MR) + AddCap ("me=\\E[0m:"); + if ((CS && SR) || AL || aflag) { + AddCap ("sr=\\EM:"); + AddCap ("al=\\E[L:"); + AddCap ("AL=\\E[%dL:"); + } + if (CS || DL || aflag) { + AddCap ("dl=\\E[M:"); + AddCap ("DL=\\E[%dM:"); + } + if (CS) + AddCap ("cs=\\E[%i%d;%dr:"); + if (DC || aflag) { + AddCap ("dc=\\E[P:"); + AddCap ("DC=\\E[%dP:"); + } + if (IC || IM || aflag) { + AddCap ("im=\\E[4h:"); + AddCap ("ei=\\E[4l:"); + AddCap ("ic=:"); + AddCap ("IC=\\E[%d@:"); + } + if (KS) + AddCap ("ks=\\E=:"); + if (KE) + AddCap ("ke=\\E>:"); + for (pp = KeyCaps; *pp; ++pp) + if (p = tgetstr (*pp, &tp)) { + MakeString (*pp, buf, p); + AddCap (buf); + } + return Termcap; +} + +MakeString (cap, buf, s) char *cap, *buf; register char *s; { + register char *p = buf; + register unsigned c; + + *p++ = *cap++; *p++ = *cap; *p++ = '='; + while (c = *s++) { + switch (c) { + case '\033': + *p++ = '\\'; *p++ = 'E'; break; + case ':': + sprintf (p, "\\072"); p += 4; break; + case '^': case '\\': + *p++ = '\\'; *p++ = c; break; + default: + if (c >= 200) { + sprintf (p, "\\%03o", c & 0377); p += 4; + } else if (c < ' ') { + *p++ = '^'; *p++ = c + '@'; + } else *p++ = c; + } + } + *p++ = ':'; *p = '\0'; +} + +Activate (wp) struct win *wp; { + RemoveStatus (wp); + curr = wp; + display = 1; + NewRendition (GlobalAttr, curr->LocalAttr); + GlobalAttr = curr->LocalAttr; + InsertMode (curr->insert); + KeypadMode (curr->keypad); + if (CS) + PutStr (tgoto (CS, curr->bot, curr->top)); + Redisplay (); +} + +ResetScreen (p) register struct win *p; { + register i; + + bzero (p->tabs, cols); + for (i = 8; i < cols; i += 8) + p->tabs[i] = 1; + p->wrap = 1; + p->origin = 0; + p->insert = 0; + p->vbwait = 0; + p->keypad = 0; + p->top = 0; + p->bot = rows - 1; + p->saved = 0; + p->LocalAttr = 0; + p->x = p->y = 0; + p->state = LIT; + p->StringType = NONE; +} + +WriteString (wp, buf, len) struct win *wp; register char *buf; { + register c, intermediate = 0; + + if (!len) + return; + curr = wp; + display = curr->active; + if (display) + RemoveStatus (wp); + do { + c = *buf++; + if (c == '\0' || c == '\177') + continue; +NextChar: + switch (curr->state) { + case TERM: + switch (c) { + case '\\': + curr->state = LIT; + *(curr->stringp) = '\0'; + if (curr->StringType == PM && display) { + MakeStatus (curr->string, curr); + if (status && len > 1) { + curr->outlen = len-1; + bcopy (buf, curr->outbuf, curr->outlen); + return; + } + } + break; + default: + curr->state = STR; + AddChar ('\033'); + AddChar (c); + } + break; + case STR: + switch (c) { + case '\0': + break; + case '\033': + curr->state = TERM; break; + default: + AddChar (c); + } + break; + case ESC: + switch (c) { + case '[': + NumArgs = 0; + intermediate = 0; + bzero ((char *)args, MAXARGS * sizeof (int)); + bzero (GotArg, MAXARGS); + curr->state = CSI; + break; + case ']': + StartString (OSC); break; + case '_': + StartString (APC); break; + case 'P': + StartString (DCS); break; + case '^': + StartString (PM); break; + default: + if (Special (c)) + break; + if (c >= ' ' && c <= '/') { + intermediate = intermediate ? -1 : c; + } else if (c >= '0' && c <= '~') { + DoESC (c, intermediate); + curr->state = LIT; + } else { + curr->state = LIT; + goto NextChar; + } + } + break; + case CSI: + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (NumArgs < MAXARGS) { + args[NumArgs] = 10 * args[NumArgs] + c - '0'; + GotArg[NumArgs] = 1; + } + break; + case ';': case ':': + NumArgs++; break; + default: + if (Special (c)) + break; + if (c >= '@' && c <= '~') { + NumArgs++; + DoCSI (c, intermediate); + curr->state = LIT; + } else if ((c >= ' ' && c <= '/') || (c >= '<' && c <= '?')) { + intermediate = intermediate ? -1 : c; + } else { + curr->state = LIT; + goto NextChar; + } + } + break; + default: + if (!Special (c)) { + if (c == '\033') { + intermediate = 0; + curr->state = ESC; + } else if (c < ' ') { + break; + } else if (curr->x < cols-1) { + if (curr->insert) { + InsertAChar (c); + } else { + if (display) + putchar (c); + SetChar (c); + } + curr->x++; + } else if (curr->x == cols-1) { + SetChar (c); + if (!(AM && curr->y == rows-1)) { + if (display) + putchar (c); + Goto (-1, -1, curr->y, curr->x); + } + curr->x++; + } else { + if (curr->wrap) { + Return (); + LineFeed (); + if (curr->insert) { + InsertAChar (c); + } else { + if (display) + putchar (c); + SetChar (c); + } + curr->x = 1; + } else curr->x = cols; + } + } + } + } while (--len); + curr->outlen = 0; +} + +static Special (c) register c; { + switch (c) { + case '\b': + BackSpace (); return 1; + case '\r': + Return (); return 1; + case '\n': + LineFeed (); return 1; + case '\007': + PutStr (BL); + if (!display) + curr->bell = 1; + return 1; + case '\t': + ForwardTab (); return 1; + } + return 0; +} + +static DoESC (c, intermediate) { + switch (intermediate) { + case 0: + switch (c) { + case 'E': + Return (); + LineFeed (); + break; + case 'D': + LineFeed (); + break; + case 'M': + ReverseLineFeed (); + break; + case 'H': + curr->tabs[curr->x] = 1; + break; + case '7': + SaveCursor (); + break; + case '8': + RestoreCursor (); + break; + case 'c': + ClearScreen (); + Goto (curr->y, curr->x, 0, 0); + NewRendition (GlobalAttr, 0); + SetRendition (0); + if (curr->insert) + InsertMode (0); + if (curr->keypad) + KeypadMode (0); + if (CS) + PutStr (tgoto (CS, rows-1, 0)); + ResetScreen (curr); + break; + case '=': + KeypadMode (1); + curr->keypad = 1; + break; + case '>': + KeypadMode (0); + curr->keypad = 0; + break; + } + break; + case '#': + switch (c) { + case '8': + FillWithEs (); + break; + } + break; + } +} + +static DoCSI (c, intermediate) { + register i, a1 = args[0], a2 = args[1]; + + if (NumArgs >= MAXARGS) + NumArgs = MAXARGS; + for (i = 0; i < NumArgs; ++i) + if (args[i] == 0) + GotArg[i] = 0; + switch (intermediate) { + case 0: + switch (c) { + case 'H': case 'f': + if (!GotArg[0]) a1 = 1; + if (!GotArg[1]) a2 = 1; + if (curr->origin) + a1 += curr->top; + if (a1 < 1) + a1 = 1; + if (a1 > rows) + a1 = rows; + if (a2 < 1) + a2 = 1; + if (a2 > cols) + a2 = cols; + a1--; a2--; + Goto (curr->y, curr->x, a1, a2); + curr->y = a1; + curr->x = a2; + break; + case 'J': + if (!GotArg[0] || a1 < 0 || a1 > 2) + a1 = 0; + switch (a1) { + case 0: + ClearToEOS (); break; + case 1: + ClearFromBOS (); break; + case 2: + ClearScreen (); + Goto (0, 0, curr->y, curr->x); + break; + } + break; + case 'K': + if (!GotArg[0] || a1 < 0 || a2 > 2) + a1 = 0; + switch (a1) { + case 0: + ClearToEOL (); break; + case 1: + ClearFromBOL (); break; + case 2: + ClearLine (); break; + } + break; + case 'A': + CursorUp (GotArg[0] ? a1 : 1); + break; + case 'B': + CursorDown (GotArg[0] ? a1 : 1); + break; + case 'C': + CursorRight (GotArg[0] ? a1 : 1); + break; + case 'D': + CursorLeft (GotArg[0] ? a1 : 1); + break; + case 'm': + SelectRendition (); + break; + case 'g': + if (!GotArg[0] || a1 == 0) + curr->tabs[curr->x] = 0; + else if (a1 == 3) + bzero (curr->tabs, cols); + break; + case 'r': + if (!CS) + break; + if (!GotArg[0]) a1 = 1; + if (!GotArg[1]) a2 = rows; + if (a1 < 1 || a2 > rows || a1 >= a2) + break; + curr->top = a1-1; + curr->bot = a2-1; + PutStr (tgoto (CS, curr->bot, curr->top)); + if (curr->origin) { + Goto (-1, -1, curr->top, 0); + curr->y = curr->top; + curr->x = 0; + } else { + Goto (-1, -1, 0, 0); + curr->y = curr->x = 0; + } + break; + case 'I': + if (!GotArg[0]) a1 = 1; + while (a1--) + ForwardTab (); + break; + case 'Z': + if (!GotArg[0]) a1 = 1; + while (a1--) + BackwardTab (); + break; + case 'L': + InsertLine (GotArg[0] ? a1 : 1); + break; + case 'M': + DeleteLine (GotArg[0] ? a1 : 1); + break; + case 'P': + DeleteChar (GotArg[0] ? a1 : 1); + break; + case '@': + InsertChar (GotArg[0] ? a1 : 1); + break; + case 'h': + SetMode (1); + break; + case 'l': + SetMode (0); + break; + } + break; + case '?': + if (c != 'h' && c != 'l') + break; + if (!GotArg[0]) + break; + i = (c == 'h'); + if (a1 == 5) { + if (i) { + curr->vbwait = 1; + } else { + if (curr->vbwait) + PutStr (VB); + curr->vbwait = 0; + } + } else if (a1 == 6) { + curr->origin = i; + if (curr->origin) { + Goto (curr->y, curr->x, curr->top, 0); + curr->y = curr->top; + curr->x = 0; + } else { + Goto (curr->y, curr->x, 0, 0); + curr->y = curr->x = 0; + } + } else if (a1 == 7) { + curr->wrap = i; + } + break; + } +} + +static PutChar (c) { + putchar (c); +} + +static PutStr (s) char *s; { + if (display && s) + tputs (s, 1, PutChar); +} + +static SetChar (c) register c; { + register struct win *p = curr; + + p->image[p->y][p->x] = c; + p->attr[p->y][p->x] = p->LocalAttr; +} + +static StartString (type) enum string_t type; { + curr->StringType = type; + curr->stringp = curr->string; + curr->state = STR; +} + +static AddChar (c) { + if (curr->stringp > curr->string+MAXSTR-1) + curr->state = LIT; + else + *(curr->stringp)++ = c; +} + +/* Insert mode is a toggle on some terminals, so we need this hack: + */ +InsertMode (on) { + if (on) { + if (!insert) + PutStr (IM); + } else if (insert) + PutStr (EI); + insert = on; +} + +/* ...and maybe keypad application mode is a toggle, too: + */ +KeypadMode (on) { + if (on) { + if (!keypad) + PutStr (KS); + } else if (keypad) + PutStr (KE); + keypad = on; +} + +static SaveCursor () { + curr->saved = 1; + curr->Saved_x = curr->x; + curr->Saved_y = curr->y; + curr->SavedLocalAttr = curr->LocalAttr; +} + +static RestoreCursor () { + if (curr->saved) { + curr->LocalAttr = curr->SavedLocalAttr; + NewRendition (GlobalAttr, curr->LocalAttr); + GlobalAttr = curr->LocalAttr; + Goto (curr->y, curr->x, curr->Saved_y, curr->Saved_x); + curr->x = curr->Saved_x; + curr->y = curr->Saved_y; + } +} + +/*ARGSUSED*/ +CountChars (c) { + StrCost++; +} + +CalcCost (s) register char *s; { + if (s) { + StrCost = 0; + tputs (s, 1, CountChars); + return StrCost; + } else return EXPENSIVE; +} + +static Goto (y1, x1, y2, x2) { + register dy, dx; + register cost = 0; + register char *s; + int CMcost, n, m; + enum move_t xm = M_NONE, ym = M_NONE; + + if (!display) + return; + if (x1 == cols || x2 == cols) { + if (x2 == cols) --x2; + goto DoCM; + } + dx = x2 - x1; + dy = y2 - y1; + if (dy == 0 && dx == 0) + return; + if (y1 == -1 || x1 == -1) { +DoCM: + PutStr (tgoto (CM, x2, y2)); + return; + } + CMcost = CalcCost (tgoto (CM, x2, y2)); + if (dx > 0) { + if ((n = RewriteCost (y1, x1, x2)) < (m = dx * NDcost)) { + cost = n; + xm = M_RW; + } else { + cost = m; + xm = M_RI; + } + } else if (dx < 0) { + cost = -dx * LEcost; + xm = M_LE; + } + if (dx && (n = RewriteCost (y1, 0, x2) + CRcost) < cost) { + cost = n; + xm = M_CR; + } + if (cost >= CMcost) + goto DoCM; + if (dy > 0) { + cost += dy * DOcost; + ym = M_DO; + } else if (dy < 0) { + cost += -dy * UPcost; + ym = M_UP; + } + if (cost >= CMcost) + goto DoCM; + if (xm != M_NONE) { + if (xm == M_LE || xm == M_RI) { + if (xm == M_LE) { + s = BC; dx = -dx; + } else s = ND; + while (dx-- > 0) + PutStr (s); + } else { + if (xm == M_CR) { + PutStr (CR); + x1 = 0; + } + if (x1 < x2) { + if (curr->insert) + InsertMode (0); + for (s = curr->image[y1]+x1; x1 < x2; x1++, s++) + putchar (*s); + if (curr->insert) + InsertMode (1); + } + } + } + if (ym != M_NONE) { + if (ym == M_UP) { + s = UP; dy = -dy; + } else s = DO; + while (dy-- > 0) + PutStr (s); + } +} + +static RewriteCost (y, x1, x2) { + register cost, dx; + register char *p = curr->attr[y]+x1; + + if (AM && y == rows-1 && x2 == cols-1) + return EXPENSIVE; + cost = dx = x2 - x1; + if (dx == 0) + return 0; + if (curr->insert) + cost += EIcost + IMcost; + do { + if (*p++ != GlobalAttr) + return EXPENSIVE; + } while (--dx); + return cost; +} + +static BackSpace () { + if (curr->x > 0) { + if (curr->x < cols) { + if (BC) + PutStr (BC); + else + Goto (curr->y, curr->x, curr->y, curr->x-1); + } + curr->x--; + } +} + +static Return () { + if (curr->x > 0) { + curr->x = 0; + PutStr (CR); + } +} + +static LineFeed () { + if (curr->y == curr->bot) { + ScrollUpMap (curr->image); + ScrollUpMap (curr->attr); + } else if (curr->y < rows-1) { + curr->y++; + } + PutStr (NL); +} + +static ReverseLineFeed () { + if (curr->y == curr->top) { + ScrollDownMap (curr->image); + ScrollDownMap (curr->attr); + if (SR) { + PutStr (SR); + } else if (AL) { + Goto (curr->top, curr->x, curr->top, 0); + PutStr (AL); + Goto (curr->top, 0, curr->top, curr->x); + } else Redisplay (); + } else if (curr->y > 0) { + CursorUp (1); + } +} + +static InsertAChar (c) { + register y = curr->y, x = curr->x; + + if (x == cols) + x--; + bcopy (curr->image[y], OldImage, cols); + bcopy (curr->attr[y], OldAttr, cols); + bcopy (curr->image[y]+x, curr->image[y]+x+1, cols-x-1); + bcopy (curr->attr[y]+x, curr->attr[y]+x+1, cols-x-1); + SetChar (c); + if (!display) + return; + if (IC || IM) { + if (!curr->insert) + InsertMode (1); + PutStr (IC); + putchar (c); + if (!curr->insert) + InsertMode (0); + } else { + RedisplayLine (OldImage, OldAttr, y, x, cols-1); + ++x; + Goto (y, last_x, y, x); + } +} + +static InsertChar (n) { + register i, y = curr->y, x = curr->x; + + if (x == cols) + return; + bcopy (curr->image[y], OldImage, cols); + bcopy (curr->attr[y], OldAttr, cols); + if (n > cols-x) + n = cols-x; + bcopy (curr->image[y]+x, curr->image[y]+x+n, cols-x-n); + bcopy (curr->attr[y]+x, curr->attr[y]+x+n, cols-x-n); + ClearInLine (0, y, x, x+n-1); + if (!display) + return; + if (IC || IM) { + if (!curr->insert) + InsertMode (1); + for (i = n; i; i--) { + PutStr (IC); + putchar (' '); + } + if (!curr->insert) + InsertMode (0); + Goto (y, x+n, y, x); + } else { + RedisplayLine (OldImage, OldAttr, y, x, cols-1); + Goto (y, last_x, y, x); + } +} + +static DeleteChar (n) { + register i, y = curr->y, x = curr->x; + + if (x == cols) + return; + bcopy (curr->image[y], OldImage, cols); + bcopy (curr->attr[y], OldAttr, cols); + if (n > cols-x) + n = cols-x; + bcopy (curr->image[y]+x+n, curr->image[y]+x, cols-x-n); + bcopy (curr->attr[y]+x+n, curr->attr[y]+x, cols-x-n); + ClearInLine (0, y, cols-n, cols-1); + if (!display) + return; + if (DC) { + for (i = n; i; i--) + PutStr (DC); + } else { + RedisplayLine (OldImage, OldAttr, y, x, cols-1); + Goto (y, last_x, y, x); + } +} + +static DeleteLine (n) { + register i, old = curr->top; + + if (n > curr->bot-curr->y+1) + n = curr->bot-curr->y+1; + curr->top = curr->y; + for (i = n; i; i--) { + ScrollUpMap (curr->image); + ScrollUpMap (curr->attr); + } + if (DL) { + Goto (curr->y, curr->x, curr->y, 0); + for (i = n; i; i--) + PutStr (DL); + Goto (curr->y, 0, curr->y, curr->x); + } else if (CS) { + PutStr (tgoto (CS, curr->bot, curr->top)); + Goto (-1, -1, curr->bot, 0); + for (i = n; i; i--) + PutStr (SF); + PutStr (tgoto (CS, curr->bot, old)); + Goto (-1, -1, curr->y, curr->x); + } else Redisplay (); + curr->top = old; +} + +static InsertLine (n) { + register i, old = curr->top; + + if (n > curr->bot-curr->y+1) + n = curr->bot-curr->y+1; + curr->top = curr->y; + for (i = n; i; i--) { + ScrollDownMap (curr->image); + ScrollDownMap (curr->attr); + } + if (AL) { + Goto (curr->y, curr->x, curr->y, 0); + for (i = n; i; i--) + PutStr (AL); + Goto (curr->y, 0, curr->y, curr->x); + } else if (CS && SR) { + PutStr (tgoto (CS, curr->bot, curr->top)); + Goto (-1, -1, curr->y, 0); + for (i = n; i; i--) + PutStr (SR); + PutStr (tgoto (CS, curr->bot, old)); + Goto (-1, -1, curr->y, curr->x); + } else Redisplay (); + curr->top = old; +} + +static ScrollUpMap (pp) char **pp; { + register char *tmp = pp[curr->top]; + + bcopy (pp+curr->top+1, pp+curr->top, + (curr->bot-curr->top) * sizeof (char *)); + if (pp == curr->image) + bclear (tmp, cols); + else + bzero (tmp, cols); + pp[curr->bot] = tmp; +} + +static ScrollDownMap (pp) char **pp; { + register char *tmp = pp[curr->bot]; + + bcopy (pp+curr->top, pp+curr->top+1, + (curr->bot-curr->top) * sizeof (char *)); + if (pp == curr->image) + bclear (tmp, cols); + else + bzero (tmp, cols); + pp[curr->top] = tmp; +} + +static ForwardTab () { + register x = curr->x; + + if (curr->tabs[x] && x < cols-1) + ++x; + while (x < cols-1 && !curr->tabs[x]) + x++; + Goto (curr->y, curr->x, curr->y, x); + curr->x = x; +} + +static BackwardTab () { + register x = curr->x; + + if (curr->tabs[x] && x > 0) + x--; + while (x > 0 && !curr->tabs[x]) + x--; + Goto (curr->y, curr->x, curr->y, x); + curr->x = x; +} + +static ClearScreen () { + register i; + + PutStr (CL); + for (i = 0; i < rows; ++i) { + bclear (curr->image[i], cols); + bzero (curr->attr[i], cols); + } +} + +static ClearFromBOS () { + register n, y = curr->y, x = curr->x; + + for (n = 0; n < y; ++n) + ClearInLine (1, n, 0, cols-1); + ClearInLine (1, y, 0, x); + Goto (curr->y, curr->x, y, x); + curr->y = y; curr->x = x; +} + +static ClearToEOS () { + register n, y = curr->y, x = curr->x; + + if (CD) + PutStr (CD); + ClearInLine (!CD, y, x, cols-1); + for (n = y+1; n < rows; n++) + ClearInLine (!CD, n, 0, cols-1); + Goto (curr->y, curr->x, y, x); + curr->y = y; curr->x = x; +} + +static ClearLine () { + register y = curr->y, x = curr->x; + + ClearInLine (1, y, 0, cols-1); + Goto (curr->y, curr->x, y, x); + curr->y = y; curr->x = x; +} + +static ClearToEOL () { + register y = curr->y, x = curr->x; + + ClearInLine (1, y, x, cols-1); + Goto (curr->y, curr->x, y, x); + curr->y = y; curr->x = x; +} + +static ClearFromBOL () { + register y = curr->y, x = curr->x; + + ClearInLine (1, y, 0, x); + Goto (curr->y, curr->x, y, x); + curr->y = y; curr->x = x; +} + +static ClearInLine (displ, y, x1, x2) { + register i, n; + + if (x1 == cols) x1--; + if (x2 == cols) x2--; + if (n = x2 - x1 + 1) { + bclear (curr->image[y]+x1, n); + bzero (curr->attr[y]+x1, n); + if (displ && display) { + if (x2 == cols-1 && CE) { + Goto (curr->y, curr->x, y, x1); + curr->y = y; curr->x = x1; + PutStr (CE); + return; + } + if (y == rows-1 && AM) + --n; + if (n == 0) + return; + SaveAttr (); + Goto (curr->y, curr->x, y, x1); + for (i = n; i > 0; i--) + putchar (' '); + curr->y = y; curr->x = x1 + n; + RestoreAttr (); + } + } +} + +static CursorRight (n) register n; { + register x = curr->x; + + if (x == cols) + return; + if ((curr->x += n) >= cols) + curr->x = cols-1; + Goto (curr->y, x, curr->y, curr->x); +} + +static CursorUp (n) register n; { + register y = curr->y; + + if ((curr->y -= n) < curr->top) + curr->y = curr->top; + Goto (y, curr->x, curr->y, curr->x); +} + +static CursorDown (n) register n; { + register y = curr->y; + + if ((curr->y += n) > curr->bot) + curr->y = curr->bot; + Goto (y, curr->x, curr->y, curr->x); +} + +static CursorLeft (n) register n; { + register x = curr->x; + + if ((curr->x -= n) < 0) + curr->x = 0; + Goto (curr->y, x, curr->y, curr->x); +} + +static SetMode (on) { + register i; + + for (i = 0; i < NumArgs; ++i) { + switch (args[i]) { + case 4: + curr->insert = on; + InsertMode (on); + break; + } + } +} + +static SelectRendition () { + register i, old = GlobalAttr; + + if (NumArgs == 0) + SetRendition (0); + else for (i = 0; i < NumArgs; ++i) + SetRendition (args[i]); + NewRendition (old, GlobalAttr); +} + +static SetRendition (n) register n; { + switch (n) { + case 0: + GlobalAttr = 0; break; + case 1: + GlobalAttr |= A_BD; break; + case 2: + GlobalAttr |= A_DI; break; + case 3: + GlobalAttr |= A_SO; break; + case 4: + GlobalAttr |= A_US; break; + case 5: + GlobalAttr |= A_BL; break; + case 7: + GlobalAttr |= A_RV; break; + case 22: + GlobalAttr &= ~(A_BD|A_SO|A_DI); break; + case 23: + GlobalAttr &= ~A_SO; break; + case 24: + GlobalAttr &= ~A_US; break; + case 25: + GlobalAttr &= ~A_BL; break; + case 27: + GlobalAttr &= ~A_RV; break; + } + curr->LocalAttr = GlobalAttr; +} + +static NewRendition (old, new) register old, new; { + register i; + + if (old == new) + return; + for (i = 1; i <= A_MAX; i <<= 1) { + if ((old & i) && !(new & i)) { + PutStr (UE); + PutStr (SE); + PutStr (ME); + if (new & A_US) PutStr (US); + if (new & A_SO) PutStr (SO); + if (new & A_BL) PutStr (MB); + if (new & A_BD) PutStr (MD); + if (new & A_DI) PutStr (MH); + if (new & A_RV) PutStr (MR); + return; + } + } + if ((new & A_US) && !(old & A_US)) + PutStr (US); + if ((new & A_SO) && !(old & A_SO)) + PutStr (SO); + if ((new & A_BL) && !(old & A_BL)) + PutStr (MB); + if ((new & A_BD) && !(old & A_BD)) + PutStr (MD); + if ((new & A_DI) && !(old & A_DI)) + PutStr (MH); + if ((new & A_RV) && !(old & A_RV)) + PutStr (MR); +} + +static SaveAttr () { + NewRendition (GlobalAttr, 0); + if (curr->insert) + InsertMode (0); +} + +static RestoreAttr () { + NewRendition (0, GlobalAttr); + if (curr->insert) + InsertMode (1); +} + +static FillWithEs () { + register i; + register char *p, *ep; + + curr->y = curr->x = 0; + NewRendition (GlobalAttr, 0); + SetRendition (0); + for (i = 0; i < rows; ++i) { + bzero (curr->attr[i], cols); + p = curr->image[i]; + ep = p + cols; + for ( ; p < ep; ++p) + *p = 'E'; + } + Redisplay (); +} + +static Redisplay () { + register i; + + PutStr (CL); + TmpAttr = GlobalAttr; + last_x = last_y = 0; + for (i = 0; i < rows; ++i) + DisplayLine (blank, null, curr->image[i], curr->attr[i], i, 0, cols-1); + NewRendition (TmpAttr, GlobalAttr); + Goto (last_y, last_x, curr->y, curr->x); +} + +static DisplayLine (os, oa, s, as, y, from, to) + register char *os, *oa, *s, *as; { + register i, x, a; + + if (to == cols) + --to; + if (AM && y == rows-1 && to == cols-1) + --to; + a = TmpAttr; + for (x = i = from; i <= to; ++i, ++x) { + if (s[i] == os[i] && as[i] == oa[i] && as[i] == a) + continue; + Goto (last_y, last_x, y, x); + last_y = y; + last_x = x; + if ((a = as[i]) != TmpAttr) { + NewRendition (TmpAttr, a); + TmpAttr = a; + } + putchar (s[i]); + last_x++; + } +} + +static RedisplayLine (os, oa, y, from, to) char *os, *oa; { + if (curr->insert) + InsertMode (0); + NewRendition (GlobalAttr, 0); + TmpAttr = 0; + last_y = y; + last_x = from; + DisplayLine (os, oa, curr->image[y], curr->attr[y], y, from, to); + NewRendition (TmpAttr, GlobalAttr); + if (curr->insert) + InsertMode (1); +} + +static MakeBlankLine (p, n) register char *p; register n; { + do *p++ = ' '; + while (--n); +} + +MakeStatus (msg, wp) char *msg; struct win *wp; { + struct win *ocurr = curr; + int odisplay = display; + register char *s, *t; + register max = AM ? cols-1 : cols; + + for (s = t = msg; *s && t - msg < max; ++s) + if (*s >= ' ' && *s <= '~') + *t++ = *s; + *t = '\0'; + curr = wp; + display = 1; + if (status) { + if (time ((time_t *)0) - TimeDisplayed < 2) + sleep (1); + RemoveStatus (wp); + } + if (t > msg) { + status = 1; + StatLen = t - msg; + Goto (curr->y, curr->x, rows-1, 0); + NewRendition (GlobalAttr, A_SO); + if (curr->insert) + InsertMode (0); + printf ("%s", msg); + if (curr->insert) + InsertMode (1); + NewRendition (A_SO, GlobalAttr); + fflush (stdout); + time (&TimeDisplayed); + } + curr = ocurr; + display = odisplay; +} + +RemoveStatus (p) struct win *p; { + struct win *ocurr = curr; + int odisplay = display; + + if (!status) + return; + status = 0; + curr = p; + display = 1; + Goto (-1, -1, rows-1, 0); + RedisplayLine (null, null, rows-1, 0, StatLen); + Goto (rows-1, last_x, curr->y, curr->x); + curr = ocurr; + display = odisplay; +} diff --git a/src/screen.1 b/src/screen.1 new file mode 100644 index 0000000..a7171bf --- /dev/null +++ b/src/screen.1 @@ -0,0 +1,619 @@ +.\" @(#)screen.1 1.3 88/07/20 01:04:04 +.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 x\fP or \fBC-a C-x\fP" +Lock the terminal and don not continue execution until the terminal is released +.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 + lock Lock the terminal + 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 ENVIRONMENT +.nf +LOCKPRG path of the lock-program used in lock-function +.br +LOCKMSG message to be issued if the terminal is unlocked again +.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. +.PP +The lock-function should not depend on external programs but call an internal +function to avoid security troubles diff --git a/src/screen.c b/src/screen.c new file mode 100644 index 0000000..0870f36 --- /dev/null +++ b/src/screen.c @@ -0,0 +1,1655 @@ +/* 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.7 88/12/13"; + +#ifdef SUNOS +#define KVMLIB +#endif SUNOS + +#include <stdio.h> +#include <sgtty.h> +#include <signal.h> +#include <errno.h> +#include <ctype.h> +#include <sys/types.h> +#include <utmp.h> +#include <pwd.h> +#include <nlist.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 KVMLIB +#include <kvm.h> +#include <sys/param.h> +#endif KVMLIB + +#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(); +extern struct passwd *getpwuid(); +#ifdef pe3200 +extern struct utmp *SetUtmp(); +#endif /* pe3200 */ +static Finit(), SigChld(); +static char *MakeBellMsg(), *Filename(), **SaveArgs(); +#ifdef LOCK +static void LockTerminal(); +static char LockEnd[] = "Welcome back to screen !!"; +#endif LOCK + +static char PtyName[32], TtyName[32]; +static char *ShellProg; +static char *ShellArgs[2]; +static char inbuf[IOSIZE]; +static inlen; +static ESCseen; +static GotSignal; +#ifdef LOCK +static char DefaultLock[] = "/usr/local/bin/lck"; +#endif LOCK +static char DefaultShell[] = "/bin/sh"; +#ifdef pe3200 +static char DefaultPath[] = ":/usr/local/bin:/bin:/usr/bin"; +#else +static char DefaultPath[] = ":/usr/ucb:/bin:/usr/bin"; +#endif +static char PtyProto[] = "/dev/ptyXY"; +static char TtyProto[] = "/dev/ttyXY"; +#ifdef SEQUENT +#define PTYCHAR1 "p" +#define PTYCHAR2 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#endif SEQUENT +#ifndef PTYCHAR1 +#define PTYCHAR1 "pqrst" +#endif PTYCHAR1 +#ifndef PTYCHAR2 +#define PTYCHAR2 "01213456789abcdef" +#endif PTYCHAR2 +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; +#ifndef pe3200 +static utmpf; +static char UtmpName[] = "/etc/utmp"; +#endif +static char *LoginName; +static char *BellString = "Bell in window %"; +static mflag, nflag, fflag; +static char HostName[MAXSTR]; +#ifdef LOADAV +#ifdef SEQUENT + static char AvenrunSym[] = "_loadav"; +#else SEQUENT + static char AvenrunSym[] = "_avenrun"; +#endif + static struct nlist nl[2]; + static avok; +#ifdef KVMLIB + static kvm_t *kmemf; +#else + static char KmemName[] = "/dev/kmem"; +#ifdef SEQUENT + static char UnixName[] = "/dynix"; +#else SEQUENT + static char UnixName[] = "/vmunix"; +#endif SEQUENT + static kmemf; +#endif KVMLIB +#ifndef vax +#define AVvar(X) long X[3] +#ifndef FSCALE +#define FSCALE 1 +#endif FSCALE +#define AVfmt(X) (((double)X)/FSCALE) +#else +#define AVvar(X) double X[3] +#define AVfmt(X) X +#endif vax +#endif LOADAV + +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 +#ifdef LOCK +#define KEY_LOCK 26 +#define KEY_CREATE 27 +#else LOCK +#define KEY_CREATE 26 +#endif LOCK + +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", +#ifdef LOCK + "lock", +#endif LOCK + 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); + signal (SIGCHLD, SigChld); + 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); + tv.tv_usec = 0; + while (1) { +#ifdef EBUG +fprintf(stderr,"DEBUG: next while. "); +#endif * EBUG */ + 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); +#ifdef EBUG +fprintf(stderr,"DEBUG: selecting.. "); +#endif * EBUG */ + if (select (32, &r, &w, &x, status ? &tv : (struct timeval *)0) == -1) { + if (errno == EINTR) + continue; + HasWindow = 0; + Msg (errno, "select"); + /*NOTREACHED*/ + } +#ifdef EBUG +fprintf(stderr,"DEBUG: select DONE. "); +#endif * EBUG */ + if (GotSignal && !status) { + SigHandler (); +#ifdef EBUG +fprintf(stderr,"DEBUG: handle sigs "); +#endif * EBUG */ + continue; + } + if (r & 1 << s) { + RemoveStatus (curr); + ReceiveMsg (s); + } + if (r & 1 << 0) { + RemoveStatus (curr); + if (ESCseen) { + inbuf[0] = Esc; +#ifdef EBUG +fprintf(stderr,"DEBUG: read(ESC) "); +#endif * EBUG */ + inlen = read (0, inbuf+1, IOSIZE-1) + 1; + ESCseen = 0; + } else { +#ifdef EBUG +fprintf(stderr,"DEBUG: read(nonESC) "); +#endif * EBUG */ + inlen = read (0, inbuf, IOSIZE); + } + if (inlen > 0) + inlen = ProcessInput (inbuf, inlen); +#ifdef EBUG +fprintf(stderr,"DEBUG: Input DONE. \n"); +#endif * EBUG */ + if (inlen > 0) + continue; + } + if (GotSignal && !status) { + SigHandler (); +#ifdef EBUG +fprintf(stderr,"DEBUG: handle sigs2 "); +#endif * EBUG */ + 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 (); +#ifdef EBUG +fprintf(stderr,"DEBUG: handle sigs3"); +#endif * EBUG */ + 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; +#ifdef LOCK + ktab['x'].type = ktab[Ctrl('x')].type = KEY_LOCK; +#endif LOCK + 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; +#ifdef LOCK + case KEY_LOCK: + p = buf; + LockTerminal(); + break; +#endif LOCK + 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]; + +#ifdef EBUG +fprintf(stderr,"DEBUG: makewindow(%s)\n",prog); +#endif /* EBUG */ + 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; + int i; +#ifdef LOADAV + AVvar(av); +#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 (avok && GetAvenrun (av)) { + p = buf + strlen (buf); + for (i = 0; i < sizeof(av)/sizeof(av[0]); i++) { + /* sprintf (p, " %2.2f" , AVfmt(av[i])), */ + sprintf (p, " %g" , AVfmt(av[i])), + p = buf + strlen (buf); + } + } +#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 = PTYCHAR1; *p = *l; ++l) { + for (d = PTYCHAR2; 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; +#ifdef pe3200 + /* this is because this is really a System V + * In CBREAK mode eof = min number of chars to be read before return + * eol = time in 1/10 seconds waiting for input + */ + np->m_tchars.t_eofc = 1; + np->m_tchars.t_brkc = 1; +#endif + 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."); +#ifdef SEQUENT + /* the sequent has problems with binding AF_UNIX sockets + * to NFS files. So we changed the path to point to a (hopefully) + * local directory + */ + sprintf (SockPath, "/tmp/.%s.%d", SockDir, getuid()); +#else SEQUENT + sprintf (SockPath, "/tmp/.%s.%d", home, SockDir); +#endif SEQUENT + 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; + +#ifndef pe3200 + if ((utmpf = open (UtmpName, O_WRONLY)) == -1) { + if (errno != EACCES) + Msg (errno, UtmpName); + return; + } +#endif + if ((LoginName = getlogin ()) == 0 || LoginName[0] == '\0') { + if ((p = getpwuid (getuid ())) == 0) + LoginName = "SCREEN"; + else + LoginName = p->pw_name; + } + utmp = 1; +} + +static +#ifdef pe3200 + struct utmp * +#endif + SetUtmp (name) char *name; { + register char *p; +#ifdef pe3200 + struct utmp *u; +#else + register struct ttyent *tp; + register slot = 1; + struct utmp u; + + if (!utmp) + return 0; +#endif + if (p = rindex (name, '/')) + ++p; + else p = name; +#ifndef pe3200 + 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); + time (&u.ut_time); + u.ut_host[0] = '\0'; + (void) lseek (utmpf, (long)(slot * sizeof (u)), 0); + (void) write (utmpf, &u, sizeof (u)); + return slot; +#else + if ( (u = (struct utmp *)malloc(sizeof(struct utmp)) == (struct utmp *)NULL) ) + { + return NULL; + } + strncpy (u->ut_line, p, 12); + u->ut_id[0] = *p; + strncpy(&u->ut_id[1],strlen(p) > 1 ? p + strlen(p) - 2 : p, 2); + u->ut_id[3] = '\0'; + strncpy (u->ut_user, LoginName, 8); + time (&u->ut_time); + u->ut_type = USER_PROCESS; + u->ut_pid = getpid(); + pututline(u); + return u; +#endif +} + +static RemoveUtmp (slot) +#ifdef pe3200 + struct utmp *slot; +#endif +{ +#ifdef pe3200 + if (slot) + { + slot->ut_type = DEAD_PROCESS; + pututline(slot); + } +#else + struct utmp u; + + if (slot) { + bzero (&u, sizeof (u)); + (void) lseek (utmpf, (long)(slot * sizeof (u)), 0); + (void) write (utmpf, &u, sizeof (u)); + } +#endif +} + +#ifdef LOCK + +/* ADDED by Rainer Pruy 10/15/87 */ +static void LockTerminal() +{ + char *prg; + int sig, pid; + void * sigs[NSIG]; + + for ( sig = 1; sig < NSIG; sig++ ) + { + sigs[sig] = (void *)signal(sig,SIG_IGN); + } + SetTTY (0, &OldMode); + FinitTerm (); + printf("\n"); + + if ( (pid = fork()) == 0) + { + /* Child */ + if ( (prg = getenv("LOCKPRG")) == NULL) + prg = DefaultLock; + + execl(prg,"SCREEN-LOCK",NULL); + Msg(errno,"%s",prg); + sleep(2); + exit(0); + } + if (pid == -1) + { + SetTTY (0, &NewMode); + Activate (wtab[CurrNum]); + Msg(errno,"Cannot lock terminal - fork failed"); + } + else + { + union wait status; + int wret, saverrno; + + errno = 0; + while ( ((wret=wait(&status)) != pid) || + ((wret == -1) && (errno = EINTR)) + ) errno = 0; + + saverrno = errno; + SetTTY (0, &NewMode); + Activate (wtab[CurrNum]); + + if (saverrno) + Msg(saverrno,"Lock"); + else + if (status.w_T.w_Termsig != 0) + Msg(0,"Lock: %s: Killed by signal: %d%s",prg, + status.w_T.w_Termsig,status.w_T.w_Coredump?" (Core dumped)":""); + else + if (status.w_T.w_Retcode) + Msg(0,"Lock: %s: Returnecode = %d",prg,status.w_T.w_Retcode); + else + { + int startpos; + char *endmsg = getenv("LOCKMSG"); + + if ( endmsg == NULL ) endmsg = LockEnd; + startpos = (cols - strlen(endmsg)) / 2 - 1; + if ( startpos < 0 ) startpos = 0; + Msg(0,"%*s%-*s", startpos, "", cols - startpos, endmsg); + } + } + /* reset signals */ + for ( sig = 1; sig < NSIG; sig++ ) + { + signal(sig,(int (*)())sigs[sig]); + } +} /* LockTerminal */ + +#endif LOCK + + +#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 () { + avok = 0; + nl[0].n_name = AvenrunSym; +#ifdef KVMLIB + if ((kmemf = kvm_open (NULL, NULL, NULL, O_RDONLY, "screen")) == NULL) + return; + kvm_nlist (kmemf, nl); +#else KVMLIB + if ((kmemf = open (KmemName, O_RDONLY)) == -1) + return; + nlist (UnixName, nl); +#endif KVMLIB + if (nl[0].n_type == 0 || nl[0].n_value == 0) + return; + avok = 1; +} + +static GetAvenrun (av) AVvar(av); { +#ifdef KVMLIB + if (kvm_read( kmemf, nl[0].n_value, av, sizeof (av)) != sizeof(av)) + return 0; +#else KVMLIB + if (lseek (kmemf, nl[0].n_value, 0) == -1) + return 0; + if (read (kmemf, av, sizeof (av)) != sizeof (av)) + return 0; +#endif KVMLIB + return 1; +} + +#endif diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 0000000..9cf4aea --- /dev/null +++ b/src/screen.h @@ -0,0 +1,68 @@ +/* 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. + */ + +#ifndef NO_SCCS_ID +static char SCCS_screen_h[]="@(#)screen.h 1.3 88/07/20 01:58:01"; +#endif /* NO_SCCS_ID */ + +enum state_t { + LIT, /* Literal input */ + ESC, /* Start of escape sequence */ + STR, /* Start of control string */ + TERM, /* ESC seen in control string */ + CSI, /* Reading arguments in "CSI Pn ; Pn ; ... ; XXX" */ +}; + +enum string_t { + NONE, + DCS, /* Device control string */ + OSC, /* Operating system command */ + APC, /* Application program command */ + PM, /* Privacy message */ +}; + +#define MAXSTR 128 + +#define IOSIZE 80 + +struct win { + int wpid; + int ptyfd; + int aflag; + char outbuf[IOSIZE]; + int outlen; + char cmd[MAXSTR]; + char tty[MAXSTR]; +#ifdef pe3200 + struct utmp *slot; +#else + int slot; +#endif + char **image; + char **attr; + int active; + int x, y; + char LocalAttr; + int saved; + int Saved_x, Saved_y; + char SavedLocalAttr; + int top, bot; + int wrap; + int origin; + int insert; + int keypad; + enum state_t state; + enum string_t StringType; + char string[MAXSTR]; + char *stringp; + char *tabs; + int vbwait; + int bell; +}; |