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