resize.c revision 50027b5b
1/* $XTermId: resize.c,v 1.148 2022/02/18 20:32:48 tom Exp $ */
2
3/*
4 * Copyright 2003-2021,2022 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#ifdef RESIZE_ONLY
61#include "resize.h"
62#else
63#include <xterm.h>
64#include <version.h>
65#include <xstrings.h>
66#include <xtermcap.h>
67#include <xterm_io.h>
68#endif
69
70#ifndef USE_TERMINFO		/* avoid conflict with configure script */
71#if defined(__QNX__) || defined(__SCO__) || defined(linux) || defined(__OpenBSD__) || defined(__UNIXWARE__)
72#define USE_TERMINFO
73#endif
74#endif
75
76#if defined(__QNX__)
77#include <unix.h>
78#endif
79
80/*
81 * Some OS's may want to use both, like SCO for example.  We catch here anyone
82 * who hasn't decided what they want.
83 */
84#if !defined(USE_TERMCAP) && !defined(USE_TERMINFO)
85#define USE_TERMINFO
86#endif
87
88#include <signal.h>
89#include <pwd.h>
90
91#ifdef USE_IGNORE_RC
92int ignore_unused;
93#endif
94
95#ifdef __MVS__
96#define ESCAPE(string) "\047" string
97#else
98#define ESCAPE(string) "\033" string
99#endif
100
101#define	EMULATIONS	2
102#define	SUN		1
103#define	VT100		0
104
105#define	TIMEOUT		10
106
107#define	SHELL_UNKNOWN	0
108#define	SHELL_C		1
109#define	SHELL_BOURNE	2
110/* *INDENT-OFF* */
111static struct {
112    const char *name;
113    int type;
114} shell_list[] = {
115    { "csh",	SHELL_C },	/* vanilla cshell */
116    { "jcsh",   SHELL_C },
117    { "tcsh",   SHELL_C },
118    { "sh",	SHELL_BOURNE }, /* vanilla Bourne shell */
119    { "ash",    SHELL_BOURNE },
120    { "bash",	SHELL_BOURNE }, /* GNU Bourne again shell */
121    { "dash",	SHELL_BOURNE },
122    { "jsh",    SHELL_BOURNE },
123    { "ksh",	SHELL_BOURNE }, /* Korn shell (from AT&T toolchest) */
124    { "ksh-i",	SHELL_BOURNE }, /* another name for Korn shell */
125    { "ksh93",	SHELL_BOURNE }, /* Korn shell */
126    { "mksh",   SHELL_BOURNE },
127    { "pdksh",  SHELL_BOURNE },
128    { "zsh",    SHELL_BOURNE },
129    { NULL,	SHELL_BOURNE }	/* default (same as xterm's) */
130};
131/* *INDENT-ON* */
132
133static const char *const emuname[EMULATIONS] =
134{
135    "VT100",
136    "Sun",
137};
138static char *myname;
139static int shell_type = SHELL_UNKNOWN;
140static const char *const getsize[EMULATIONS] =
141{
142    ESCAPE("7") ESCAPE("[r") ESCAPE("[9999;9999H") ESCAPE("[6n"),
143    ESCAPE("[18t"),
144};
145#if defined(USE_STRUCT_WINSIZE)
146static const char *const getwsize[EMULATIONS] =
147{				/* size in pixels */
148    0,
149    ESCAPE("[14t"),
150};
151#endif /* USE_STRUCT_WINSIZE */
152static const char *const restore[EMULATIONS] =
153{
154    ESCAPE("8"),
155    0,
156};
157static const char *const setsize[EMULATIONS] =
158{
159    0,
160    ESCAPE("[8;%s;%st"),
161};
162
163#ifdef USE_ANY_SYSV_TERMIO
164static struct termio tioorig;
165#elif defined(USE_TERMIOS)
166static struct termios tioorig;
167#else
168static struct sgttyb sgorig;
169#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
170
171static const char *const size[EMULATIONS] =
172{
173    ESCAPE("[%d;%dR"),
174    ESCAPE("[8;%d;%dt"),
175};
176static const char sunname[] = "sunsize";
177static int tty;
178static FILE *ttyfp;
179
180#if defined(USE_STRUCT_WINSIZE)
181static const char *wsize[EMULATIONS] =
182{
183    0,
184    ESCAPE("[4;%hd;%hdt"),
185};
186#endif /* USE_STRUCT_WINSIZE */
187
188static GCC_NORETURN void failed(const char *);
189static GCC_NORETURN void onintr(int);
190static GCC_NORETURN void resize_timeout(int);
191static GCC_NORETURN void Usage(void);
192
193static void
194failed(const char *s)
195{
196    int save = errno;
197    IGNORE_RC(write(2, myname, strlen(myname)));
198    IGNORE_RC(write(2, ": ", (size_t) 2));
199    errno = save;
200    perror(s);
201    exit(EXIT_FAILURE);
202}
203
204/* ARGSUSED */
205static void
206onintr(int sig GCC_UNUSED)
207{
208#ifdef USE_ANY_SYSV_TERMIO
209    (void) ioctl(tty, TCSETAW, &tioorig);
210#elif defined(USE_TERMIOS)
211    (void) tcsetattr(tty, TCSADRAIN, &tioorig);
212#else /* not USE_TERMIOS */
213    (void) ioctl(tty, TIOCSETP, &sgorig);
214#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
215    exit(EXIT_FAILURE);
216}
217
218static void
219resize_timeout(int sig)
220{
221    fprintf(stderr, "\n%s: Time out occurred\r\n", myname);
222    onintr(sig);
223}
224
225static void
226Usage(void)
227{
228    fprintf(stderr, strcmp(myname, sunname) == 0 ?
229	    "Usage: %s [rows cols]\n" :
230	    "Usage: %s [-v] [-u] [-c] [-s [rows cols]]\n", myname);
231    exit(EXIT_FAILURE);
232}
233
234#ifdef USE_TERMCAP
235static void
236print_termcap(const char *termcap)
237{
238    int ch;
239
240    putchar('\'');
241    while ((ch = *termcap++) != '\0') {
242	switch (ch & 0xff) {
243	case 127:		/* undo bug in GNU termcap */
244	    printf("^?");
245	    break;
246	case '\'':		/* must escape anyway (unlikely) */
247	    /* FALLTHRU */
248	case '!':		/* must escape for SunOS csh */
249	    putchar('\\');
250	    /* FALLTHRU */
251	default:
252	    putchar(ch);
253	    break;
254	}
255    }
256    putchar('\'');
257}
258#endif /* USE_TERMCAP */
259
260static int
261checkdigits(char *str)
262{
263    while (*str) {
264	if (!isdigit(CharOf(*str)))
265	    return (0);
266	str++;
267    }
268    return (1);
269}
270
271static void
272unexpected_char(int c)
273{
274    fprintf(stderr, "%s: unknown character %#x, exiting.\r\n", myname, c);
275    onintr(0);
276}
277
278static void
279readstring(FILE *fp, char *buf, const char *str)
280{
281    int last, c;
282#if !defined(USG) && !defined(__minix)
283    /* What is the advantage of setitimer() over alarm()? */
284    struct itimerval it;
285#endif
286    int limit = (BUFSIZ - 3);
287
288    signal(SIGALRM, resize_timeout);
289#if defined(USG) || defined(__minix)
290    alarm(TIMEOUT);
291#else
292    memset((char *) &it, 0, sizeof(struct itimerval));
293    it.it_value.tv_sec = TIMEOUT;
294    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
295#endif
296    if ((c = getc(fp)) == 0233) {	/* meta-escape, CSI */
297	c = ESCAPE("")[0];
298	*buf++ = (char) c;
299	*buf++ = '[';
300    } else {
301	*buf++ = (char) c;
302    }
303    if (c != *str) {
304	unexpected_char(c);
305    }
306    last = str[strlen(str) - 1];
307    while ((c = getc(fp)) != EOF) {
308	if (--limit <= 0) {
309	    fprintf(stderr, "%s: unexpected response\n", myname);
310	    onintr(0);
311	}
312	if (c < 32 || c > 126) {
313	    unexpected_char(c);
314	}
315	*buf++ = (char) c;
316	if (c == last)
317	    break;
318    }
319#if defined(USG) || defined(__minix)
320    alarm(0);
321#else
322    memset((char *) &it, 0, sizeof(struct itimerval));
323    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
324#endif
325    *buf = 0;
326}
327
328/*
329   resets termcap string to reflect current screen size
330 */
331int
332main(int argc, char **argv ENVP_ARG)
333{
334#ifdef USE_TERMCAP
335    char *env;
336#endif
337    char *ptr;
338    int emu = VT100;
339    char *shell;
340    int i;
341    int rc;
342    int rows, cols;
343#ifdef USE_ANY_SYSV_TERMIO
344    struct termio tio;
345#elif defined(USE_TERMIOS)
346    struct termios tio;
347#else
348    struct sgttyb sg;
349#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
350#ifdef USE_TERMCAP
351    int ok_tcap = 1;
352    char termcap[TERMCAP_SIZE];
353    char newtc[TERMCAP_SIZE];
354#endif /* USE_TERMCAP */
355    char buf[BUFSIZ];
356#ifdef TTYSIZE_STRUCT
357    TTYSIZE_STRUCT ts;
358#endif
359    char *name_of_tty;
360#ifdef CANT_OPEN_DEV_TTY
361    extern char *ttyname();
362#endif
363    const char *setname = "";
364
365    myname = x_basename(argv[0]);
366    if (strcmp(myname, sunname) == 0)
367	emu = SUN;
368    for (argv++, argc--; argc > 0 && **argv == '-'; argv++, argc--) {
369	switch ((*argv)[1]) {
370	case 's':		/* Sun emulation */
371	    if (emu == SUN)
372		Usage();	/* Never returns */
373	    emu = SUN;
374	    break;
375	case 'u':		/* Bourne (Unix) shell */
376	    shell_type = SHELL_BOURNE;
377	    break;
378	case 'c':		/* C shell */
379	    shell_type = SHELL_C;
380	    break;
381	case 'v':
382	    printf("%s\n", xtermVersion());
383	    exit(EXIT_SUCCESS);
384	default:
385	    Usage();		/* Never returns */
386	}
387    }
388
389    if (SHELL_UNKNOWN == shell_type) {
390	/* Find out what kind of shell this user is running.
391	 * This is the same algorithm that xterm uses.
392	 */
393	if ((ptr = x_getenv("SHELL")) == NULL) {
394	    uid_t uid = getuid();
395	    struct passwd pw;
396
397	    if (x_getpwuid(uid, &pw)) {
398		(void) x_getlogin(uid, &pw);
399	    }
400	    if (!OkPasswd(&pw)
401		|| *(ptr = pw.pw_shell) == 0) {
402		/* this is the same default that xterm uses */
403		ptr = x_strdup("/bin/sh");
404	    }
405	}
406
407	shell = x_basename(ptr);
408
409	/* now that we know, what kind is it? */
410	for (i = 0; shell_list[i].name; i++) {
411	    if (!strcmp(shell_list[i].name, shell)) {
412		break;
413	    }
414	}
415	shell_type = shell_list[i].type;
416    }
417
418    if (argc == 2) {
419	if (!setsize[emu]) {
420	    fprintf(stderr,
421		    "%s: Can't set window size under %s emulation\n",
422		    myname, emuname[emu]);
423	    exit(EXIT_FAILURE);
424	}
425	if (!checkdigits(argv[0]) || !checkdigits(argv[1])) {
426	    Usage();		/* Never returns */
427	}
428    } else if (argc != 0) {
429	Usage();		/* Never returns */
430    }
431#ifdef CANT_OPEN_DEV_TTY
432    if ((name_of_tty = ttyname(fileno(stderr))) == NULL)
433#endif
434	name_of_tty = x_strdup("/dev/tty");
435
436    if ((ttyfp = fopen(name_of_tty, "r+")) == NULL) {
437	fprintf(stderr, "%s:  can't open terminal %s\n",
438		myname, name_of_tty);
439	exit(EXIT_FAILURE);
440    }
441    tty = fileno(ttyfp);
442#ifdef USE_TERMCAP
443    if ((env = x_getenv("TERM")) == 0) {
444	env = x_strdup(DFT_TERMTYPE);
445	if (SHELL_BOURNE == shell_type) {
446	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
447	} else {
448	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
449	}
450    }
451    termcap[0] = 0;		/* ...just in case we've accidentally gotten terminfo */
452    if (tgetent(termcap, env) <= 0 || termcap[0] == 0) {
453	ok_tcap = 0;
454    }
455#endif /* USE_TERMCAP */
456#ifdef USE_TERMINFO
457    if (x_getenv("TERM") == 0) {
458	if (SHELL_BOURNE == shell_type) {
459	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
460	} else {
461	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
462	}
463    }
464#endif /* USE_TERMINFO */
465
466#ifdef USE_ANY_SYSV_TERMIO
467    rc = ioctl(tty, TCGETA, &tioorig);
468    tio = tioorig;
469    UIntClr(tio.c_iflag, (ICRNL | IUCLC));
470    UIntClr(tio.c_lflag, (ICANON | ECHO));
471    tio.c_cflag |= CS8;
472    tio.c_cc[VMIN] = 6;
473    tio.c_cc[VTIME] = 1;
474#elif defined(USE_TERMIOS)
475    rc = tcgetattr(tty, &tioorig);
476    tio = tioorig;
477    UIntClr(tio.c_iflag, ICRNL);
478    UIntClr(tio.c_lflag, (ICANON | ECHO));
479    tio.c_cflag |= CS8;
480    tio.c_cc[VMIN] = 6;
481    tio.c_cc[VTIME] = 1;
482#else /* not USE_TERMIOS */
483    rc = ioctl(tty, TIOCGETP, &sgorig);
484    sg = sgorig;
485    sg.sg_flags |= RAW;
486    UIntClr(sg.sg_flags, ECHO);
487#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
488    if (rc != 0)
489	failed("get tty settings");
490
491    signal(SIGINT, onintr);
492    signal(SIGQUIT, onintr);
493    signal(SIGTERM, onintr);
494
495#ifdef USE_ANY_SYSV_TERMIO
496    rc = ioctl(tty, TCSETAW, &tio);
497#elif defined(USE_TERMIOS)
498    rc = tcsetattr(tty, TCSADRAIN, &tio);
499#else /* not USE_TERMIOS */
500    rc = ioctl(tty, TIOCSETP, &sg);
501#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
502    if (rc != 0)
503	failed("set tty settings");
504
505    if (argc == 2) {		/* look for optional parameters of "-s" */
506	char *tmpbuf = TypeMallocN(char,
507				   strlen(setsize[emu]) +
508				   strlen(argv[0]) +
509				   strlen(argv[1]) +
510				   1);
511	if (tmpbuf == 0) {
512	    fprintf(stderr, "%s: Cannot query size\n", myname);
513	    onintr(0);
514	} else {
515	    sprintf(tmpbuf, setsize[emu], argv[0], argv[1]);
516	    IGNORE_RC(write(tty, tmpbuf, strlen(tmpbuf)));
517	    free(tmpbuf);
518	}
519    }
520    IGNORE_RC(write(tty, getsize[emu], strlen(getsize[emu])));
521    readstring(ttyfp, buf, size[emu]);
522    if (sscanf(buf, size[emu], &rows, &cols) != 2) {
523	fprintf(stderr, "%s: Can't get rows and columns\r\n", myname);
524	onintr(0);
525    }
526    if (restore[emu])
527	IGNORE_RC(write(tty, restore[emu], strlen(restore[emu])));
528#if defined(USE_STRUCT_WINSIZE)
529    /* finally, set the tty's window size */
530    if (getwsize[emu]) {
531	/* get the window size in pixels */
532	IGNORE_RC(write(tty, getwsize[emu], strlen(getwsize[emu])));
533	readstring(ttyfp, buf, wsize[emu]);
534	if (sscanf(buf, wsize[emu], &ts.ws_xpixel, &ts.ws_ypixel) != 2) {
535	    fprintf(stderr, "%s: Can't get window size\r\n", myname);
536	    onintr(0);
537	}
538	setup_winsize(ts, rows, cols, 0, 0);
539	SET_TTYSIZE(tty, ts);
540    } else if (ioctl(tty, TIOCGWINSZ, &ts) != -1) {
541	/* we don't have any way of directly finding out
542	   the current height & width of the window in pixels.  We try
543	   our best by computing the font height and width from the "old"
544	   window-size values, and multiplying by these ratios... */
545#define scaled(old,new,len) (old)?((unsigned)(new)*(len)/(old)):(len)
546	setup_winsize(ts, rows, cols,
547		      scaled(TTYSIZE_ROWS(ts), rows, ts.ws_ypixel),
548		      scaled(TTYSIZE_COLS(ts), cols, ts.ws_xpixel));
549	SET_TTYSIZE(tty, ts);
550    }
551#endif /* USE_STRUCT_WINSIZE */
552
553#ifdef USE_ANY_SYSV_TERMIO
554    rc = ioctl(tty, TCSETAW, &tioorig);
555#elif defined(USE_TERMIOS)
556    rc = tcsetattr(tty, TCSADRAIN, &tioorig);
557#else /* not USE_TERMIOS */
558    rc = ioctl(tty, TIOCSETP, &sgorig);
559#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
560    if (rc != 0)
561	failed("set tty settings");
562
563    signal(SIGINT, SIG_DFL);
564    signal(SIGQUIT, SIG_DFL);
565    signal(SIGTERM, SIG_DFL);
566
567#ifdef USE_TERMCAP
568    if (ok_tcap) {
569	/* update termcap string */
570	/* first do columns */
571	if ((ptr = x_strindex(termcap, "co#")) == NULL) {
572	    fprintf(stderr, "%s: No `co#'\n", myname);
573	    exit(EXIT_FAILURE);
574	}
575
576	i = (int) (ptr - termcap) + 3;
577	strncpy(newtc, termcap, (size_t) i);
578	sprintf(newtc + i, "%d", cols);
579	if ((ptr = strchr(ptr, ':')) != 0)
580	    strcat(newtc, ptr);
581
582	/* now do lines */
583	if ((ptr = x_strindex(newtc, "li#")) == NULL) {
584	    fprintf(stderr, "%s: No `li#'\n", myname);
585	    exit(EXIT_FAILURE);
586	}
587
588	i = (int) (ptr - newtc) + 3;
589	strncpy(termcap, newtc, (size_t) i);
590	sprintf(termcap + i, "%d", rows);
591	if ((ptr = strchr(ptr, ':')) != 0)
592	    strcat(termcap, ptr);
593    }
594#endif /* USE_TERMCAP */
595
596    if (SHELL_BOURNE == shell_type) {
597
598#ifdef USE_TERMCAP
599	if (ok_tcap) {
600	    printf("%sTERMCAP=", setname);
601	    print_termcap(termcap);
602	    printf(";\nexport TERMCAP;\n");
603	}
604#endif /* USE_TERMCAP */
605#ifdef USE_TERMINFO
606	printf("%sCOLUMNS=%d;\nLINES=%d;\nexport COLUMNS LINES;\n",
607	       setname, cols, rows);
608#endif /* USE_TERMINFO */
609
610    } else {			/* not Bourne shell */
611
612#ifdef USE_TERMCAP
613	if (ok_tcap) {
614	    printf("set noglob;\n%ssetenv TERMCAP ", setname);
615	    print_termcap(termcap);
616	    printf(";\nunset noglob;\n");
617	}
618#endif /* USE_TERMCAP */
619#ifdef USE_TERMINFO
620	printf("set noglob;\n%ssetenv COLUMNS '%d';\nsetenv LINES '%d';\nunset noglob;\n",
621	       setname, cols, rows);
622#endif /* USE_TERMINFO */
623    }
624    exit(EXIT_SUCCESS);
625}
626