* Copyright (c) 1980 Regents of the University of California.
* %sccs.include.redist.c%
"@(#) Copyright (c) 1980 Regents of the University of California.\n\
static char sccsid
[] = "@(#)tset.c 5.13 (Berkeley) 6/1/90";
** TSET -- set terminal modes
** This program does sophisticated terminal initialization.
** I recommend that you include it in your .profile or .login
** file to initialize whatever terminal you are on.
** There are several features:
** A special file or sequence (as controlled by the termcap file)
** is sent to the terminal.
** Mode bits are set on a per-terminal_type basis.
** This allows special delays, automatic tabs, etc.
** Erase and Kill characters can be set to whatever you want.
** Default is to change erase to control-H on a terminal which
** can overstrike, and leave it alone on anything else. Kill
** is always left alone unless specifically requested. These
** characters can be represented as "^X" meaning control-X;
** Terminals which are dialups or plugboard types can be aliased
** to whatever type you may have in your home or office. Thus,
** if you know that when you dial up you will always be on a
** TI 733, you can specify that fact to tset. You can represent
** a type as "?type". This will ask you what type you want it
** to be -- if you reply with just a newline, it will default
** The current terminal type can be queried.
** tset [-] [-hnrsIQS] [-eC] [-iC] [-kC] [-EC]
** [-m [port-type][test baudrate]:terminal-type] [terminal-type]
** In systems with environments, use:
** Actually, this doesn't work in old csh's.
** tset -s ... > tset.tmp
** set term=(`tset -S ....`)
** setenv TERMCAP "$term[2]"
** Positional Parameters:
** type -- the terminal type to force. If this is
** specified, initialization is for this
** - -- report terminal type. Whatever type is
** decided on is reported. If no other flags
** are stated, the only affect is to write
** the terminal type on the standard output.
** -r -- report to user in addition to other flags.
** -EC -- set the erase character to C on all terminals
** except those which cannot backspace (e.g.,
** -eC -- set the erase character to C on all terminals.
** the erase character is untouched; however, if
** not specified and the erase character is NULL
** (zero byte), the erase character is set to delete.
** -kC -- set the kill character to C on all terminals.
** the kill character is untouched; however, if
** not specified and the kill character is NULL
** (zero byte), the kill character is set to control-U.
** -iC -- set the interrupt character to C on all terminals.
** interrupt character is untouched; however, if
** not specified and the interrupt character is NULL
** (zero byte), the interrupt character is set to
** -qC -- reserved for settable quit character.
** -m -- map the system identified type to some user
** specified type. The mapping can be baud rate
** dependent. This replaces the old -d, -p flags.
** (-d type -> -m dialup:type)
** (-p type -> -m plug:type)
** Syntax: -m identifier [test baudrate] :type
** where: ``identifier'' is terminal type found in
** /etc/ttys for this port, (absence of an identifier
** matches any identifier); ``test'' may be any
** combination of > = < ! @; ``baudrate'' is as with
** stty (1); ``type'' is the actual terminal type to use
** if the mapping condition is met. Multiple maps are
** scanned in order and the first match prevails.
** -h -- don't read terminal type from environment.
** -s -- output setenv commands for TERM. This can be
** and is to be prefered to:
** setenv TERM `tset - ...`
** because -s sets the TERMCAP variable also.
** -S -- Similar to -s but outputs 2 strings suitable for
** use in csh .login files as follows:
** set term=(`tset -S .....`)
** setenv TERMCAP "$term[2]"
** -Q -- be quiet. don't output 'Erase set to' etc.
** -I -- don't do terminal initialization (is & if
** contains a terminal id -> terminal type
** mapping; used when any user mapping is specified,
** or the environment doesn't have TERM set.
** a terminal_type -> terminal_capabilities
** 2 -- couldn't open termcap.
** 1 -- invalid terminal type, or standard error not tty.
** Electronics Research Labs
** 1/81 -- Added alias checking for mapping identifiers.
** 9/80 -- Added UCB_NTTY mods to setup the new tty driver.
** Added the 'reset ...' invocation.
** 7/80 -- '-S' added. '-m' mapping added. TERMCAP string
** 3/80 -- Changed to use tputs. Prc & flush added.
** 10/79 -- '-s' option extended to handle TERMCAP
** variable, set noglob, quote the entry,
** and know about the Bourne shell. Terminal
** initialization moved to before any information
** output so screen clears would not screw you.
** 8/79 -- '-' option alone changed to only output
** type. '-s' option added. 'VERSION7'
** changed to 'V6' for compatibility.
** 12/78 -- modified for eventual migration to VAX/UNIX,
** so the '-' option is changed to output only
** the terminal type to STDOUT instead of
** 9/78 -- '-' and '-p' options added (now fully
** compatible with ttytype!), and spaces are
** permitted between the -d and the type.
** 8/78 -- The sense of -h and -u were reversed, and the
** -f flag is dropped -- same effect is available
** by just stating the terminal type.
/* Default values for control characters. */
#define _CTRL(c) ((c) & 0x1f)
#define CERASE 0177 /* DEL, ^? */
#define CFLUSH _CTRL('o')
#define CLNEXT _CTRL('v')
#define CQUIT 034 /* FS, ^\ */
#define CRPRNT _CTRL('r')
#define CSTART _CTRL('q')
#define CWERASE _CTRL('w')
/* This should be in <termcap.h> instead. */
/* This should be in <getopt.h> instead. */
void set_control_chars();
void get_termcap_entry();
void output_initializations();
#define curerase mode.c_cc[VERASE]
#define curkill mode.c_cc[VKILL]
#define curintr mode.c_cc[VINTR]
#define olderase oldmode.c_cc[VERASE]
#define oldkill oldmode.c_cc[VKILL]
#define oldintr oldmode.c_cc[VINTR]
#define CNTL(c) ((c)&037)
#define BACKSPACE (CNTL('H'))
#define CHK(val, dft) (val<=0 ? dft : val)
#define FILEDES 2 /* change attrs on this descriptor */
#define STDOUT 1 /* output -s/-S to this descriptor */
/* Last resort default term type. */
#define DEFTYPE "unknown"
/* Baud rate conditionals for mapping. */
/* Maximum number of mappings allowed. */
char *Ident
; /* Port type, or "" for any. */
char Test
; /* Baud rate conditionals bitmask. */
char Speed
; /* Baud rate to compare against. */
char *Type
; /* Terminal type to select. */
/* Next available element of `map'. */
char *string
; /* ASCII representation. */
int speed
; /* Internal form. */
int baudrate
; /* Numeric value. */
char Erase_char
; /* new erase character */
char Kill_char
; /* new kill character */
char Intr_char
; /* new interrupt character */
char Specialerase
; /* set => Erase_char only on terminals with backspace (-E) */
char *TtyPath
= NULL
; /* terminal device */
char *TtyType
= NULL
; /* type of terminal */
char *DefType
= NULL
; /* default type if none other computed */
int Mapped
; /* mapping has been specified */
int NoTermFromEnv
; /* don't get terminal type from environ (-h) */
int DoSetenv
; /* output TERMCAP strings (-s, -S) */
int CmndLine
; /* output setenv command lines (-s) */
int BeQuiet
; /* Don't say ctrl key settings (-Q, reset) */
int NoInit
; /* don't output initialization string (-I) */
int IsReset
; /* invoked as reset */
int Report
; /* report term type on stdout (-) */
int Ureport
; /* report term type to user on stderr (-r) */
int RepOnly
; /* only report term type (- & no other args) */
int Ask
; /* ask user for termtype */
int PadBaud
; /* Min rate of padding needed, -1 if none */
char Capbuf
[CAPBUFSIZ
]; /* line from /etc/termcap for this TtyType */
char *Ttycap
; /* termcap line from termcap or environ */
/* Null-terminated array of alternative names for the terminal.
The first element is the terminal's two-letter V6 short name.
The elements are pointers into 'Aliasbuf'. */
/* The name this program was run with. */
if (tcgetattr(FILEDES
, &mode
) < 0)
fprintf(stderr
, "%s: standard error must be a terminal\n",
ospeed
= cfgetospeed(&mode
);
(void) signal(SIGINT
, set_mode
);
(void) signal(SIGQUIT
, set_mode
);
(void) signal(SIGTERM
, set_mode
);
command
= strrchr(argv
[0], '/');
if (!strcmp(command
, "reset") )
while ((optc
= getopt(argc
, argv
, "e:hi:k:m:nrsE:IQS")) != EOF
)
case 'n': /* obsolete -- ignore */
case 'r': /* report to user */
case 'E': /* special erase: operate on all but TTY33 */
/* explicit fall-through to -e case */
case 'e': /* erase character */
if (optarg
[0] == '^' && optarg
[1] != '\0')
Erase_char
= CNTL(optarg
[1]);
case 'i': /* interrupt character */
if (optarg
[0] == '^' && optarg
[1] != '\0')
Intr_char
= CNTL(optarg
[1]);
case 'k': /* kill character */
if (optarg
[0] == '^' && optarg
[1] != '\0')
Kill_char
= CNTL(optarg
[1]);
case 'm': /* map identifier to type */
case 'h': /* don't get type from env */
case 's': /* output setenv commands */
case 'S': /* output setenv strings */
case 'I': /* no initialization */
if (optind
< argc
&& !strcmp(argv
[optind
], "-"))
fprintf(stderr
, "%s: extra arguments\n", program_name
);
set_conversions(command
);
(void) ioctl(FILEDES
, TIOCGWINSZ
, (char *)&win
);
if (win
.ws_row
== 0 && win
.ws_col
== 0 &&
lines
> 0 && columns
> 0) {
(void) ioctl(FILEDES
, TIOCSWINSZ
, (char *)&win
);
output_initializations();
set_mode(0); /* set new modes, if they've changed */
/* set up environment for the shell we are using
(this code is rather heuristic, checking for $SHELL
ending in the 3 characters "csh") */
csh
= !strcmp(&sh
[i
- 3], "csh");
/* running Bourne shell */
puts("export TERMCAP TERM;");
/* report type if appropriate */
if (DoSetenv
|| Report
|| Ureport
)
/* If 'TtyType' is the short name, use the first alias
if (!strcmp(TtyType
, Alias
[0]) && Alias
[1])
fputs("setenv TERM ", stdout
);
prs("Terminal type is ");
fputs("setenv TERMCAP '", stdout
);
fputs("TERMCAP='", stdout
);
puts("';\nunset noglob;");
if(!RepOnly
&& !BeQuiet
) {
/* tell about changing erase, kill and interrupt characters */
reportek("Erase", curerase
, olderase
, CERASE
);
reportek("Kill", curkill
, oldkill
, CKILL
);
reportek("Interrupt", curintr
, oldintr
, CINTR
);
/* Syntax for P: [port-type][test baudrate]:terminal-type
The baud rate tests are: > < @/= !
The baudrate number can be preceded by a 'B', which is ignored.
The baudrate number can also be `exta' or `extb'.
This code is very loose. Almost no syntax checking is done!
However, invalid syntax will only produce weird results. */
Map
->Ident
= p
; /* Port-type identifier. */
/* Scan for optional test and baudrate. */
case ':': /* Mapped type. */
case '>': /* Conditional. */
case '<': /* Conditional. */
case '=': /* Conditional. */
case '!': /* Invert conditions. */
case 'B': /* Baud rate */
/* Intentional fallthru. */
if (isdigit(*p
) || *p
== 'e')
Map
->Speed
= baudrate (p
);
while (isalnum(*p
) || *p
== '.')
if (Not
) /* Invert sense of test. */
Map
->Test
= (~(Map
->Test
)) & ALL
;
/* Reset the terminal mode bits to a sensible state.
Very useful after crapping out in raw mode. */
tcgetattr(FILEDES
, &mode
);
curintr
= CHK(curintr
, CINTR
);
mode
.c_cc
[VQUIT
] = CHK(mode
.c_cc
[VQUIT
], CQUIT
);
curerase
= CHK(curerase
, CERASE
);
curkill
= CHK(curkill
, CKILL
);
mode
.c_cc
[VEOF
] = CHK(mode
.c_cc
[VEOF
], CEOF
);
mode
.c_cc
[VSTART
] = CHK(mode
.c_cc
[VSTART
], CSTART
);
mode
.c_cc
[VSTOP
] = CHK(mode
.c_cc
[VSTOP
], CSTOP
);
mode
.c_cc
[VSUSP
] = CHK(mode
.c_cc
[VSUSP
], CSUSP
);
#if defined(VREPRINT) && defined(CRPRNT)
mode
.c_cc
[VREPRINT
] = CHK(mode
.c_cc
[VREPRINT
], CRPRNT
);
#if defined(VWERASE) && defined(CWERASE)
mode
.c_cc
[VWERASE
] = CHK(mode
.c_cc
[VWERASE
], CWERASE
);
#if defined(VLNEXT) && defined(CLNEXT)
mode
.c_cc
[VLNEXT
] = CHK(mode
.c_cc
[VLNEXT
], CLNEXT
);
#if defined(VFLUSH) && defined(CFLUSH)
mode
.c_cc
[VFLUSH
] = CHK(mode
.c_cc
[VFLUSH
], CFLUSH
);
#if defined(VDISCARD) && defined(CDISCARD)
mode
.c_cc
[VDISCARD
] = CHK(mode
.c_cc
[VDISCARD
], CDISCARD
);
mode
.c_iflag
&= ~(IGNBRK
| PARMRK
| INPCK
| ISTRIP
| INLCR
| IGNCR
mode
.c_iflag
|= (BRKINT
| IGNPAR
| ICRNL
| IXON
| NLDLY
| CRDLY
| TABDLY
| BSDLY
| VTDLY
| FFDLY
mode
.c_cflag
&= ~(CSIZE
| CSTOPB
| PARENB
| PARODD
| CLOCAL
);
mode
.c_cflag
|= (CS8
| CREAD
);
mode
.c_lflag
&= ~(ECHONL
| NOFLSH
| TOSTOP
mode
.c_lflag
|= (ISIG
| ICANON
| ECHO
| ECHOE
| ECHOK
tcsetattr(FILEDES
, TCSADRAIN
, &mode
);
/* Determine the terminal type and place it in 'TtyType'. */
Map
->Ident
= ""; /* means "map any type" */
Map
->Test
= ALL
; /* at all baud rates */
Map
->Type
= DefType
; /* to the default type */
/* Get rid of $TERMCAP, if it's there, so we get a real
entry from /etc/termcap. This prevents us from being
fooled by out of date stuff in the environment. */
bufp
= getenv("TERMCAP");
if (bufp
!= NULL
&& *bufp
!= '/')
strcpy(bufp
- 8, "NOTHING"); /* Overwrite only "TERMCAP". */
/* Get current idea of terminal type from environment. */
if (!NoTermFromEnv
&& TtyType
== NULL
)
TtyType
= getenv("TERM");
/* Determine terminal id if needed. */
if (!RepOnly
&& TtyPath
== NULL
&& (TtyType
== NULL
|| !NoTermFromEnv
))
TtyPath
= ttyname(FILEDES
);
/* If still undefined, look at /etc/ttytype. */
TtyType
= stypeof(TtyPath
);
/* If still undefined, use DEFTYPE. */
/* Check for dialup or other mapping. */
if (Alias
[0] == NULL
|| !isalias(TtyType
))
if (tgetent(Capbuf
, TtyType
) > 0)
TtyType
= mapped(TtyType
);
/* TtyType now contains a pointer to the type of the terminal.
If the first character is '?', ask the user. */
/* Query the user for TtyType if needed, then
read the termcap entry for TtyType into Capbuf and TTycap. */
/* Read the terminal. If not empty, set type. */
i
= read(2, termbuf
, sizeof termbuf
- 1);
if (termbuf
[i
- 1] == '\n')
/* Get terminal capabilities. */
if (Alias
[0] == NULL
|| !isalias(TtyType
))
switch (tgetent(Capbuf
, TtyType
))
prs(": cannot find termcap\n");
/* Determine the erase, interrupt, and kill characters
from the termcap entry and command line
and update their values in 'mode'. */
if (Specialerase
&& !tgetflag("bs"))
p
= tgetstr("kb", &bufp
);
if (p
== NULL
|| p
[1] != '\0')
p
= tgetstr("bc", &bufp
);
if (p
!= NULL
&& p
[1] == '\0')
if (Erase_char
== 0 && !tgetflag("os") && curerase
== CERASE
)
if (tgetflag("bs") || bs_char
!= 0)
Erase_char
= (bs_char
!= 0) ? bs_char
: BACKSPACE
;
/* Set up various conversions in 'mode', including
parity, tabs, returns, echo, and case, according to
if 'command' starts with uppercase, map external uppercase
to internal lowercase. */
if (tgetflag("UC") || (command
[0] & 0140) == 0100)
mode
.c_iflag
&= ~(PARMRK
| INPCK
);
/* Newline, not linefeed. */
mode
.c_lflag
|= (ECHOE
| ECHOK
);
* Set the hardware tabs on the terminal, using the ct (clear all tabs),
* st (set one tab) and ch (horizontal cursor addressing) capabilities.
* This is done before if and is, so they can patch in case we blow this.
* Return nonzero if we set any tab stops, zero if not.
char *clear_tabs
, *set_tab
, *set_column
, *set_pos
= NULL
;
clear_tabs
= tgetstr("ct", &capsp
);
set_tab
= tgetstr("st", &capsp
);
set_column
= tgetstr("ch", &capsp
);
set_pos
= tgetstr("cm", &capsp
);
if (clear_tabs
&& set_tab
) {
prc('\r'); /* force to be at left margin */
tputs(clear_tabs
, 0, prc
);
for (c
=8; c
<columns
; c
+= 8) {
/* get to that column. */
tg_out
= "OOPS"; /* also returned by tgoto */
tg_out
= tgoto(set_column
, 0, c
);
if (*tg_out
== 'O' && set_pos
)
tg_out
= tgoto(set_pos
, c
, lines
-1);
/* Output startup string. */
if (tgetstr("pc", &bufp
) != 0)
if (oldmode
.c_oflag
& (TAB3
| ONLCR
| OCRNL
| ONLRET
))
oldmode
.c_oflag
&= (TAB3
| ONLCR
| OCRNL
| ONLRET
);
if (IsReset
&& tgetstr("rs", &bufp
) != 0 || tgetstr("is", &bufp
) != 0)
if (IsReset
&& tgetstr("rf", &bufp
) != 0 || tgetstr("if", &bufp
) != 0)
sleep(1); /* Let the terminal settle down. */
/* Set the terminal processing according to 'mode' or 'oldmode'.
If called as the result of a signal, flag is > 0.
If called from terminal init, flag == -1 means reset 'oldmode'.
If called at end of normal mode processing, flag == 0. */
tcsetattr(FILEDES
, TCSADRAIN
, &oldmode
);
else if (memcmp((char *) &mode
, (char *) &oldmode
, sizeof mode
))
tcsetattr(FILEDES
, TCSADRAIN
, &mode
);
if (flag
> 0) /* trapped signal */
/* Tell the user the value that a control key is set to,
if it has been changed from the default value.
'new', 'old', and 'def' are the current, previous, and default
ASCII values for the control key called 'name' in English. */
reportek(name
, new, old
, def
)
register unsigned char o
;
register unsigned char n
;
if (tgetstr("kb", &bufp
) && n
== buf
[0] && buf
[1] == '\0')
/* Print string 's' to stderr, buffered. */
/* Print character 'c' to stderr, buffered. */
OutBuf
[OutPtr
++] = c
& 0x7f;
if (OutPtr
>= sizeof OutBuf
)
/* Write out and empty the buffer used by prs and prc. */
(void) write(2, OutBuf
, OutPtr
);
/* Print file 'file' to STDERR. */
while ((i
= read(fd
, buf
, BUFSIZ
)) > 0)
/* Put each of the alternative names for the terminal
described by the termcap entry 'buf' into an element of 'Alias'.
Make the final element a NULL. */
register int i
; /* Index into 'Alias'. */
register char *a
; /* Pointer into 'Aliasbuf'. */
register char *b
; /* Pointer into 'buf'. */
while (*b
&& *b
!= ':') {
/* Return YES if 'ident' is the same as one of the aliases,
/* Return the default type of terminal for line 'ttyid'. */
/* Set TtyId to the basename of ttyid. */
/* get aliases from termcap entry */
if (Mapped
&& tgetent(Capbuf
, PortType
) > 0) {
if (!strcmp(Alias
[0], PortType
) && Alias
[1])
#define WHITE(c) (c == ' ' || c == '\t')
/* Output termcap entry 'bp' to stdout, quoting characters that
would give the shell problems and omitting empty fields. */
/* Copy the terminal names in 'bp' into 'buf',
discarding (verbose) names that contain blanks. */
while (*bp
&& *bp
!= ':') {
while (*tp
&& *tp
!= '|' && *tp
!= ':') {
space
= (space
|| WHITE(*tp
) );
case ':': /* discard empty, cancelled or dupl fields */
while (*tp
&& *tp
!= ':') {
empty
= (empty
&& WHITE(*tp
) );
if (empty
|| cancelled(bp
+1)) {
case ' ': /* no spaces in output */
case '!': /* the shell thinks this is history */
case ',': /* the shell thinks this is history */
case '"': /* no quotes in output */
case '\'': /* no quotes in output */
case '`': /* no back quotes in output */
case '^': /* anything following is OK */
*p
++ = ':'; /* we skipped the last : with the : lookahead hack */
(void) write (STDOUT
, buf
, p
-buf
);
/* Array of termcap entries processed so far by 'cancelled',
for omitting duplicates from the output. */
/* Number of valid entries in 'delcap'. */
/* Return YES if 'cap' is a commented-out or duplicate capability,
for (i
= 0; i
< ncap
; i
++)
if (cap
[0] == delcap
[i
][0] && cap
[1] == delcap
[i
][1])
/* delete a second occurrance of the same capability */
delcap
[ncap
][0] = cap
[0];
delcap
[ncap
][1] = cap
[1];
/* Append a printable representation of 'str' to 'ptr' and
return a pointer to the next available character after 'ptr'. */
ptr
= putbuf(ptr
, "\\E");
(void) sprintf(buf
, "\\%03o", *str
);
/* Return the internal form of ASCII baud rate 'p',
or -1 if 'p' does not represent a valid baud rate. */
while (i
< 7 && (isalnum(*p
) || *p
== '.'))
for (i
=0; speeds
[i
].string
; i
++)
if (!strcmp(speeds
[i
].string
, buf
))
return (speeds
[i
].speed
);
/* Return the type of terminal to use for a port of type 'type',
as specified by the first applicable mapping in 'map'.
If no mappings apply, return 'type'. */
if (*(Map
->Ident
) == '\0'
|| !strcmp(Map
->Ident
, type
) || isalias(Map
->Ident
))
case ANY
: /* no test specified */
match
= (ospeed
> Map
->Speed
);
match
= (ospeed
>= Map
->Speed
);
match
= (ospeed
== Map
->Speed
);
match
= (ospeed
<= Map
->Speed
);
match
= (ospeed
< Map
->Speed
);
match
= (ospeed
!= Map
->Speed
);
/* no match found; return given type */
Usage: %s [-] [-hnrsIQS] [-eC] [-iC] [-kC] [-EC]\n\
[-m [port-type][test baudrate]:terminal-type] [terminal-type]\n",