resize.c revision 913cc679
1/* $XTermId: resize.c,v 1.139 2017/05/31 08:58:56 tom Exp $ */
2
3/*
4 * Copyright 2003-2015,2017 by Thomas E. Dickey
5 *
6 *                         All Rights Reserved
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the
29 * sale, use or other dealings in this Software without prior written
30 * authorization.
31 *
32 *
33 * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
34 *
35 *                         All Rights Reserved
36 *
37 * Permission to use, copy, modify, and distribute this software and its
38 * documentation for any purpose and without fee is hereby granted,
39 * provided that the above copyright notice appear in all copies and that
40 * both that copyright notice and this permission notice appear in
41 * supporting documentation, and that the name of Digital Equipment
42 * Corporation not be used in advertising or publicity pertaining to
43 * distribution of the software without specific, written prior permission.
44 *
45 *
46 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
47 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
48 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
49 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
50 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
51 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
52 * SOFTWARE.
53 */
54
55/* resize.c */
56
57#include <stdio.h>
58#include <ctype.h>
59
60#include <xterm.h>
61#include <version.h>
62#include <xstrings.h>
63#include <xtermcap.h>
64#include <xterm_io.h>
65
66#ifndef USE_TERMINFO		/* avoid conflict with configure script */
67#if defined(__QNX__) || defined(__SCO__) || defined(linux) || defined(__OpenBSD__) || defined(__UNIXWARE__)
68#define USE_TERMINFO
69#endif
70#endif
71
72#if defined(__QNX__)
73#include <unix.h>
74#endif
75
76/*
77 * Some OS's may want to use both, like SCO for example.  We catch here anyone
78 * who hasn't decided what they want.
79 */
80#if !defined(USE_TERMCAP) && !defined(USE_TERMINFO)
81#define USE_TERMINFO
82#endif
83
84#include <signal.h>
85#include <pwd.h>
86
87#ifdef USE_IGNORE_RC
88int ignore_unused;
89#endif
90
91#ifdef __MVS__
92#define ESCAPE(string) "\047" string
93#else
94#define ESCAPE(string) "\033" string
95#endif
96
97#define	EMULATIONS	2
98#define	SUN		1
99#define	VT100		0
100
101#define	TIMEOUT		10
102
103#define	SHELL_UNKNOWN	0
104#define	SHELL_C		1
105#define	SHELL_BOURNE	2
106/* *INDENT-OFF* */
107static struct {
108    const char *name;
109    int type;
110} shell_list[] = {
111    { "csh",	SHELL_C },	/* vanilla cshell */
112    { "jcsh",   SHELL_C },
113    { "tcsh",   SHELL_C },
114    { "sh",	SHELL_BOURNE }, /* vanilla Bourne shell */
115    { "ash",    SHELL_BOURNE },
116    { "bash",	SHELL_BOURNE }, /* GNU Bourne again shell */
117    { "dash",	SHELL_BOURNE },
118    { "jsh",    SHELL_BOURNE },
119    { "ksh",	SHELL_BOURNE }, /* Korn shell (from AT&T toolchest) */
120    { "ksh-i",	SHELL_BOURNE }, /* another name for Korn shell */
121    { "ksh93",	SHELL_BOURNE }, /* Korn shell */
122    { "mksh",   SHELL_BOURNE },
123    { "pdksh",  SHELL_BOURNE },
124    { "zsh",    SHELL_BOURNE },
125    { NULL,	SHELL_BOURNE }	/* default (same as xterm's) */
126};
127/* *INDENT-ON* */
128
129static const char *const emuname[EMULATIONS] =
130{
131    "VT100",
132    "Sun",
133};
134static char *myname;
135static int shell_type = SHELL_UNKNOWN;
136static const char *const getsize[EMULATIONS] =
137{
138    ESCAPE("7") ESCAPE("[r") ESCAPE("[9999;9999H") ESCAPE("[6n"),
139    ESCAPE("[18t"),
140};
141#if defined(USE_STRUCT_WINSIZE)
142static const char *const getwsize[EMULATIONS] =
143{				/* size in pixels */
144    0,
145    ESCAPE("[14t"),
146};
147#endif /* USE_STRUCT_WINSIZE */
148static const char *const restore[EMULATIONS] =
149{
150    ESCAPE("8"),
151    0,
152};
153static const char *const setsize[EMULATIONS] =
154{
155    0,
156    ESCAPE("[8;%s;%st"),
157};
158
159#ifdef USE_ANY_SYSV_TERMIO
160static struct termio tioorig;
161#elif defined(USE_TERMIOS)
162static struct termios tioorig;
163#else
164static struct sgttyb sgorig;
165#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
166
167static const char *const size[EMULATIONS] =
168{
169    ESCAPE("[%d;%dR"),
170    ESCAPE("[8;%d;%dt"),
171};
172static const char sunname[] = "sunsize";
173static int tty;
174static FILE *ttyfp;
175
176#if defined(USE_STRUCT_WINSIZE)
177static const char *wsize[EMULATIONS] =
178{
179    0,
180    ESCAPE("[4;%hd;%hdt"),
181};
182#endif /* USE_STRUCT_WINSIZE */
183
184static void
185failed(const char *s)
186{
187    int save = errno;
188    IGNORE_RC(write(2, myname, strlen(myname)));
189    IGNORE_RC(write(2, ": ", (size_t) 2));
190    errno = save;
191    perror(s);
192    exit(EXIT_FAILURE);
193}
194
195/* ARGSUSED */
196static void
197onintr(int sig GCC_UNUSED)
198{
199#ifdef USE_ANY_SYSV_TERMIO
200    (void) ioctl(tty, TCSETAW, &tioorig);
201#elif defined(USE_TERMIOS)
202    (void) tcsetattr(tty, TCSADRAIN, &tioorig);
203#else /* not USE_TERMIOS */
204    (void) ioctl(tty, TIOCSETP, &sgorig);
205#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
206    exit(EXIT_FAILURE);
207}
208
209static void
210resize_timeout(int sig)
211{
212    fprintf(stderr, "\n%s: Time out occurred\r\n", myname);
213    onintr(sig);
214}
215
216static void
217Usage(void)
218{
219    fprintf(stderr, strcmp(myname, sunname) == 0 ?
220	    "Usage: %s [rows cols]\n" :
221	    "Usage: %s [-v] [-u] [-c] [-s [rows cols]]\n", myname);
222    exit(EXIT_FAILURE);
223}
224
225#ifdef USE_TERMCAP
226static void
227print_termcap(const char *termcap)
228{
229    int ch;
230
231    putchar('\'');
232    while ((ch = *termcap++) != '\0') {
233	switch (ch & 0xff) {
234	case 127:		/* undo bug in GNU termcap */
235	    printf("^?");
236	    break;
237	case '\'':		/* must escape anyway (unlikely) */
238	    /* FALLTHRU */
239	case '!':		/* must escape for SunOS csh */
240	    putchar('\\');
241	    /* FALLTHRU */
242	default:
243	    putchar(ch);
244	    break;
245	}
246    }
247    putchar('\'');
248}
249#endif /* USE_TERMCAP */
250
251static int
252checkdigits(char *str)
253{
254    while (*str) {
255	if (!isdigit(CharOf(*str)))
256	    return (0);
257	str++;
258    }
259    return (1);
260}
261
262static void
263readstring(FILE *fp, char *buf, const char *str)
264{
265    int last, c;
266#if !defined(USG) && !defined(__minix)
267    /* What is the advantage of setitimer() over alarm()? */
268    struct itimerval it;
269#endif
270
271    signal(SIGALRM, resize_timeout);
272#if defined(USG) || defined(__minix)
273    alarm(TIMEOUT);
274#else
275    memset((char *) &it, 0, sizeof(struct itimerval));
276    it.it_value.tv_sec = TIMEOUT;
277    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
278#endif
279    if ((c = getc(fp)) == 0233) {	/* meta-escape, CSI */
280	c = ESCAPE("")[0];
281	*buf++ = (char) c;
282	*buf++ = '[';
283    } else {
284	*buf++ = (char) c;
285    }
286    if (c != *str) {
287	fprintf(stderr, "%s: unknown character, exiting.\r\n", myname);
288	onintr(0);
289    }
290    last = str[strlen(str) - 1];
291    while ((*buf++ = (char) getc(fp)) != last) {
292	;
293    }
294#if defined(USG) || defined(__minix)
295    alarm(0);
296#else
297    memset((char *) &it, 0, sizeof(struct itimerval));
298    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
299#endif
300    *buf = 0;
301}
302
303/*
304   resets termcap string to reflect current screen size
305 */
306int
307main(int argc, char **argv ENVP_ARG)
308{
309#ifdef USE_TERMCAP
310    char *env;
311#endif
312    char *ptr;
313    int emu = VT100;
314    char *shell;
315    int i;
316    int rc;
317    int rows, cols;
318#ifdef USE_ANY_SYSV_TERMIO
319    struct termio tio;
320#elif defined(USE_TERMIOS)
321    struct termios tio;
322#else
323    struct sgttyb sg;
324#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
325#ifdef USE_TERMCAP
326    int ok_tcap = 1;
327    char termcap[TERMCAP_SIZE];
328    char newtc[TERMCAP_SIZE];
329#endif /* USE_TERMCAP */
330    char buf[BUFSIZ];
331#ifdef TTYSIZE_STRUCT
332    TTYSIZE_STRUCT ts;
333#endif
334    char *name_of_tty;
335#ifdef CANT_OPEN_DEV_TTY
336    extern char *ttyname();
337#endif
338    const char *setname = "";
339
340    myname = x_basename(argv[0]);
341    if (strcmp(myname, sunname) == 0)
342	emu = SUN;
343    for (argv++, argc--; argc > 0 && **argv == '-'; argv++, argc--) {
344	switch ((*argv)[1]) {
345	case 's':		/* Sun emulation */
346	    if (emu == SUN)
347		Usage();	/* Never returns */
348	    emu = SUN;
349	    break;
350	case 'u':		/* Bourne (Unix) shell */
351	    shell_type = SHELL_BOURNE;
352	    break;
353	case 'c':		/* C shell */
354	    shell_type = SHELL_C;
355	    break;
356	case 'v':
357	    printf("%s\n", xtermVersion());
358	    exit(EXIT_SUCCESS);
359	default:
360	    Usage();		/* Never returns */
361	}
362    }
363
364    if (SHELL_UNKNOWN == shell_type) {
365	/* Find out what kind of shell this user is running.
366	 * This is the same algorithm that xterm uses.
367	 */
368	if ((ptr = x_getenv("SHELL")) == NULL) {
369	    uid_t uid = getuid();
370	    struct passwd pw;
371
372	    if (x_getpwuid(uid, &pw)) {
373		(void) x_getlogin(uid, &pw);
374	    }
375	    if (!OkPasswd(&pw)
376		|| *(ptr = pw.pw_shell) == 0) {
377		/* this is the same default that xterm uses */
378		ptr = x_strdup("/bin/sh");
379	    }
380	}
381
382	shell = x_basename(ptr);
383
384	/* now that we know, what kind is it? */
385	for (i = 0; shell_list[i].name; i++) {
386	    if (!strcmp(shell_list[i].name, shell)) {
387		break;
388	    }
389	}
390	shell_type = shell_list[i].type;
391    }
392
393    if (argc == 2) {
394	if (!setsize[emu]) {
395	    fprintf(stderr,
396		    "%s: Can't set window size under %s emulation\n",
397		    myname, emuname[emu]);
398	    exit(EXIT_FAILURE);
399	}
400	if (!checkdigits(argv[0]) || !checkdigits(argv[1])) {
401	    Usage();		/* Never returns */
402	}
403    } else if (argc != 0) {
404	Usage();		/* Never returns */
405    }
406#ifdef CANT_OPEN_DEV_TTY
407    if ((name_of_tty = ttyname(fileno(stderr))) == NULL)
408#endif
409	name_of_tty = x_strdup("/dev/tty");
410
411    if ((ttyfp = fopen(name_of_tty, "r+")) == NULL) {
412	fprintf(stderr, "%s:  can't open terminal %s\n",
413		myname, name_of_tty);
414	exit(EXIT_FAILURE);
415    }
416    tty = fileno(ttyfp);
417#ifdef USE_TERMCAP
418    if ((env = x_getenv("TERM")) == 0) {
419	env = DFT_TERMTYPE;
420	if (SHELL_BOURNE == shell_type) {
421	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
422	} else {
423	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
424	}
425    }
426    termcap[0] = 0;		/* ...just in case we've accidentally gotten terminfo */
427    if (tgetent(termcap, env) <= 0 || termcap[0] == 0) {
428	ok_tcap = 0;
429    }
430#endif /* USE_TERMCAP */
431#ifdef USE_TERMINFO
432    if (x_getenv("TERM") == 0) {
433	if (SHELL_BOURNE == shell_type) {
434	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
435	} else {
436	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
437	}
438    }
439#endif /* USE_TERMINFO */
440
441#ifdef USE_ANY_SYSV_TERMIO
442    rc = ioctl(tty, TCGETA, &tioorig);
443    tio = tioorig;
444    UIntClr(tio.c_iflag, (ICRNL | IUCLC));
445    UIntClr(tio.c_lflag, (ICANON | ECHO));
446    tio.c_cflag |= CS8;
447    tio.c_cc[VMIN] = 6;
448    tio.c_cc[VTIME] = 1;
449#elif defined(USE_TERMIOS)
450    rc = tcgetattr(tty, &tioorig);
451    tio = tioorig;
452    UIntClr(tio.c_iflag, ICRNL);
453    UIntClr(tio.c_lflag, (ICANON | ECHO));
454    tio.c_cflag |= CS8;
455    tio.c_cc[VMIN] = 6;
456    tio.c_cc[VTIME] = 1;
457#else /* not USE_TERMIOS */
458    rc = ioctl(tty, TIOCGETP, &sgorig);
459    sg = sgorig;
460    sg.sg_flags |= RAW;
461    UIntClr(sg.sg_flags, ECHO);
462#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
463    if (rc != 0)
464	failed("get tty settings");
465
466    signal(SIGINT, onintr);
467    signal(SIGQUIT, onintr);
468    signal(SIGTERM, onintr);
469
470#ifdef USE_ANY_SYSV_TERMIO
471    rc = ioctl(tty, TCSETAW, &tio);
472#elif defined(USE_TERMIOS)
473    rc = tcsetattr(tty, TCSADRAIN, &tio);
474#else /* not USE_TERMIOS */
475    rc = ioctl(tty, TIOCSETP, &sg);
476#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
477    if (rc != 0)
478	failed("set tty settings");
479
480    if (argc == 2) {		/* look for optional parameters of "-s" */
481	char *tmpbuf = TypeMallocN(char,
482				   strlen(setsize[emu]) +
483				   strlen(argv[0]) +
484				   strlen(argv[1]) +
485				   1);
486	if (tmpbuf == 0) {
487	    fprintf(stderr, "%s: Cannot query size\n", myname);
488	    onintr(0);
489	} else {
490	    sprintf(tmpbuf, setsize[emu], argv[0], argv[1]);
491	    IGNORE_RC(write(tty, tmpbuf, strlen(tmpbuf)));
492	    free(tmpbuf);
493	}
494    }
495    IGNORE_RC(write(tty, getsize[emu], strlen(getsize[emu])));
496    readstring(ttyfp, buf, size[emu]);
497    if (sscanf(buf, size[emu], &rows, &cols) != 2) {
498	fprintf(stderr, "%s: Can't get rows and columns\r\n", myname);
499	onintr(0);
500    }
501    if (restore[emu])
502	IGNORE_RC(write(tty, restore[emu], strlen(restore[emu])));
503#if defined(USE_STRUCT_WINSIZE)
504    /* finally, set the tty's window size */
505    if (getwsize[emu]) {
506	/* get the window size in pixels */
507	IGNORE_RC(write(tty, getwsize[emu], strlen(getwsize[emu])));
508	readstring(ttyfp, buf, wsize[emu]);
509	if (sscanf(buf, wsize[emu], &ts.ws_xpixel, &ts.ws_ypixel) != 2) {
510	    fprintf(stderr, "%s: Can't get window size\r\n", myname);
511	    onintr(0);
512	}
513	setup_winsize(ts, rows, cols, 0, 0);
514	SET_TTYSIZE(tty, ts);
515    } else if (ioctl(tty, TIOCGWINSZ, &ts) != -1) {
516	/* we don't have any way of directly finding out
517	   the current height & width of the window in pixels.  We try
518	   our best by computing the font height and width from the "old"
519	   window-size values, and multiplying by these ratios... */
520#define scaled(old,new,len) (old)?((unsigned)(new)*(len)/(old)):(len)
521	setup_winsize(ts, rows, cols,
522		      scaled(TTYSIZE_ROWS(ts), rows, ts.ws_ypixel),
523		      scaled(TTYSIZE_COLS(ts), cols, ts.ws_xpixel));
524	SET_TTYSIZE(tty, ts);
525    }
526#endif /* USE_STRUCT_WINSIZE */
527
528#ifdef USE_ANY_SYSV_TERMIO
529    rc = ioctl(tty, TCSETAW, &tioorig);
530#elif defined(USE_TERMIOS)
531    rc = tcsetattr(tty, TCSADRAIN, &tioorig);
532#else /* not USE_TERMIOS */
533    rc = ioctl(tty, TIOCSETP, &sgorig);
534#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
535    if (rc != 0)
536	failed("set tty settings");
537
538    signal(SIGINT, SIG_DFL);
539    signal(SIGQUIT, SIG_DFL);
540    signal(SIGTERM, SIG_DFL);
541
542#ifdef USE_TERMCAP
543    if (ok_tcap) {
544	/* update termcap string */
545	/* first do columns */
546	if ((ptr = x_strindex(termcap, "co#")) == NULL) {
547	    fprintf(stderr, "%s: No `co#'\n", myname);
548	    exit(EXIT_FAILURE);
549	}
550
551	i = (int) (ptr - termcap) + 3;
552	strncpy(newtc, termcap, (size_t) i);
553	sprintf(newtc + i, "%d", cols);
554	if ((ptr = strchr(ptr, ':')) != 0)
555	    strcat(newtc, ptr);
556
557	/* now do lines */
558	if ((ptr = x_strindex(newtc, "li#")) == NULL) {
559	    fprintf(stderr, "%s: No `li#'\n", myname);
560	    exit(EXIT_FAILURE);
561	}
562
563	i = (int) (ptr - newtc) + 3;
564	strncpy(termcap, newtc, (size_t) i);
565	sprintf(termcap + i, "%d", rows);
566	if ((ptr = strchr(ptr, ':')) != 0)
567	    strcat(termcap, ptr);
568    }
569#endif /* USE_TERMCAP */
570
571    if (SHELL_BOURNE == shell_type) {
572
573#ifdef USE_TERMCAP
574	if (ok_tcap) {
575	    printf("%sTERMCAP=", setname);
576	    print_termcap(termcap);
577	    printf(";\nexport TERMCAP;\n");
578	}
579#endif /* USE_TERMCAP */
580#ifdef USE_TERMINFO
581	printf("%sCOLUMNS=%d;\nLINES=%d;\nexport COLUMNS LINES;\n",
582	       setname, cols, rows);
583#endif /* USE_TERMINFO */
584
585    } else {			/* not Bourne shell */
586
587#ifdef USE_TERMCAP
588	if (ok_tcap) {
589	    printf("set noglob;\n%ssetenv TERMCAP ", setname);
590	    print_termcap(termcap);
591	    printf(";\nunset noglob;\n");
592	}
593#endif /* USE_TERMCAP */
594#ifdef USE_TERMINFO
595	printf("set noglob;\n%ssetenv COLUMNS '%d';\nsetenv LINES '%d';\nunset noglob;\n",
596	       setname, cols, rows);
597#endif /* USE_TERMINFO */
598    }
599    exit(EXIT_SUCCESS);
600}
601