resize.c revision 894e0ac8
1/* $XTermId: resize.c,v 1.130 2014/04/25 23:39:42 tom Exp $ */
2
3/*
4 * Copyright 2003-2013,2014 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 *emuname[EMULATIONS] =
130{
131    "VT100",
132    "Sun",
133};
134static char *myname;
135static int shell_type = SHELL_UNKNOWN;
136static const char *getsize[EMULATIONS] =
137{
138    ESCAPE("7") ESCAPE("[r") ESCAPE("[999;999H") ESCAPE("[6n"),
139    ESCAPE("[18t"),
140};
141#if defined(USE_STRUCT_WINSIZE)
142static const char *getwsize[EMULATIONS] =
143{				/* size in pixels */
144    0,
145    ESCAPE("[14t"),
146};
147#endif /* USE_STRUCT_WINSIZE */
148static const char *restore[EMULATIONS] =
149{
150    ESCAPE("8"),
151    0,
152};
153static const char *setname = "";
154static const char *setsize[EMULATIONS] =
155{
156    0,
157    ESCAPE("[8;%s;%st"),
158};
159
160#ifdef USE_ANY_SYSV_TERMIO
161static struct termio tioorig;
162#elif defined(USE_TERMIOS)
163static struct termios tioorig;
164#else
165static struct sgttyb sgorig;
166#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
167
168static const char *size[EMULATIONS] =
169{
170    ESCAPE("[%d;%dR"),
171    ESCAPE("[8;%d;%dt"),
172};
173static char sunname[] = "sunsize";
174static int tty;
175static FILE *ttyfp;
176
177#if defined(USE_STRUCT_WINSIZE)
178static const char *wsize[EMULATIONS] =
179{
180    0,
181    ESCAPE("[4;%hd;%hdt"),
182};
183#endif /* USE_STRUCT_WINSIZE */
184
185static void
186failed(const char *s)
187{
188    int save = errno;
189    IGNORE_RC(write(2, myname, strlen(myname)));
190    IGNORE_RC(write(2, ": ", (size_t) 2));
191    errno = save;
192    perror(s);
193    exit(EXIT_FAILURE);
194}
195
196/* ARGSUSED */
197static void
198onintr(int sig GCC_UNUSED)
199{
200#ifdef USE_ANY_SYSV_TERMIO
201    (void) ioctl(tty, TCSETAW, &tioorig);
202#elif defined(USE_TERMIOS)
203    (void) tcsetattr(tty, TCSADRAIN, &tioorig);
204#else /* not USE_TERMIOS */
205    (void) ioctl(tty, TIOCSETP, &sgorig);
206#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
207    exit(EXIT_FAILURE);
208}
209
210static void
211resize_timeout(int sig)
212{
213    fprintf(stderr, "\n%s: Time out occurred\r\n", myname);
214    onintr(sig);
215}
216
217static void
218Usage(void)
219{
220    fprintf(stderr, strcmp(myname, sunname) == 0 ?
221	    "Usage: %s [rows cols]\n" :
222	    "Usage: %s [-v] [-u] [-c] [-s [rows cols]]\n", myname);
223    exit(EXIT_FAILURE);
224}
225
226#ifdef USE_TERMCAP
227static void
228print_termcap(const char *termcap)
229{
230    int ch;
231
232    putchar('\'');
233    while ((ch = *termcap++) != '\0') {
234	switch (ch & 0xff) {
235	case 127:		/* undo bug in GNU termcap */
236	    printf("^?");
237	    break;
238	case '\'':		/* must escape anyway (unlikely) */
239	    /* FALLTHRU */
240	case '!':		/* must escape for SunOS csh */
241	    putchar('\\');
242	    /* FALLTHRU */
243	default:
244	    putchar(ch);
245	    break;
246	}
247    }
248    putchar('\'');
249}
250#endif /* USE_TERMCAP */
251
252static int
253checkdigits(char *str)
254{
255    while (*str) {
256	if (!isdigit(CharOf(*str)))
257	    return (0);
258	str++;
259    }
260    return (1);
261}
262
263static void
264readstring(FILE *fp, char *buf, const char *str)
265{
266    int last, c;
267#if !defined(USG)
268    /* What is the advantage of setitimer() over alarm()? */
269    struct itimerval it;
270#endif
271
272    signal(SIGALRM, resize_timeout);
273#if defined(USG)
274    alarm(TIMEOUT);
275#else
276    memset((char *) &it, 0, sizeof(struct itimerval));
277    it.it_value.tv_sec = TIMEOUT;
278    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
279#endif
280    if ((c = getc(fp)) == 0233) {	/* meta-escape, CSI */
281	c = ESCAPE("")[0];
282	*buf++ = (char) c;
283	*buf++ = '[';
284    } else {
285	*buf++ = (char) c;
286    }
287    if (c != *str) {
288	fprintf(stderr, "%s: unknown character, exiting.\r\n", myname);
289	onintr(0);
290    }
291    last = str[strlen(str) - 1];
292    while ((*buf++ = (char) getc(fp)) != last) {
293	;
294    }
295#if defined(USG)
296    alarm(0);
297#else
298    memset((char *) &it, 0, sizeof(struct itimerval));
299    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
300#endif
301    *buf = 0;
302}
303
304/*
305   resets termcap string to reflect current screen size
306 */
307int
308main(int argc, char **argv ENVP_ARG)
309{
310#ifdef USE_TERMCAP
311    char *env;
312#endif
313    char *ptr;
314    int emu = VT100;
315    char *shell;
316    int i;
317    int rc;
318    int rows, cols;
319#ifdef USE_ANY_SYSV_TERMIO
320    struct termio tio;
321#elif defined(USE_TERMIOS)
322    struct termios tio;
323#else
324    struct sgttyb sg;
325#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
326#ifdef USE_TERMCAP
327    int ok_tcap = 1;
328    char termcap[TERMCAP_SIZE];
329    char newtc[TERMCAP_SIZE];
330#endif /* USE_TERMCAP */
331    char buf[BUFSIZ];
332#ifdef TTYSIZE_STRUCT
333    TTYSIZE_STRUCT ts;
334#endif
335    char *name_of_tty;
336#ifdef CANT_OPEN_DEV_TTY
337    extern char *ttyname();
338#endif
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	TTYSIZE_ROWS(ts) = (ttySize_t) rows;
514	TTYSIZE_COLS(ts) = (ttySize_t) cols;
515	SET_TTYSIZE(tty, ts);
516    } else if (ioctl(tty, TIOCGWINSZ, &ts) != -1) {
517	/* we don't have any way of directly finding out
518	   the current height & width of the window in pixels.  We try
519	   our best by computing the font height and width from the "old"
520	   window-size values, and multiplying by these ratios... */
521	if (TTYSIZE_COLS(ts) != 0)
522	    ts.ws_xpixel = (ttySize_t) (cols * (ts.ws_xpixel / TTYSIZE_COLS(ts)));
523	if (TTYSIZE_ROWS(ts) != 0)
524	    ts.ws_ypixel = (ttySize_t) (rows * (ts.ws_ypixel / TTYSIZE_ROWS(ts)));
525	TTYSIZE_ROWS(ts) = (ttySize_t) rows;
526	TTYSIZE_COLS(ts) = (ttySize_t) cols;
527	SET_TTYSIZE(tty, ts);
528    }
529#endif /* USE_STRUCT_WINSIZE */
530
531#ifdef USE_ANY_SYSV_TERMIO
532    rc = ioctl(tty, TCSETAW, &tioorig);
533#elif defined(USE_TERMIOS)
534    rc = tcsetattr(tty, TCSADRAIN, &tioorig);
535#else /* not USE_TERMIOS */
536    rc = ioctl(tty, TIOCSETP, &sgorig);
537#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
538    if (rc != 0)
539	failed("set tty settings");
540
541    signal(SIGINT, SIG_DFL);
542    signal(SIGQUIT, SIG_DFL);
543    signal(SIGTERM, SIG_DFL);
544
545#ifdef USE_TERMCAP
546    if (ok_tcap) {
547	/* update termcap string */
548	/* first do columns */
549	if ((ptr = x_strindex(termcap, "co#")) == NULL) {
550	    fprintf(stderr, "%s: No `co#'\n", myname);
551	    exit(EXIT_FAILURE);
552	}
553
554	i = (int) (ptr - termcap) + 3;
555	strncpy(newtc, termcap, (size_t) i);
556	sprintf(newtc + i, "%d", cols);
557	ptr = strchr(ptr, ':');
558	strcat(newtc, ptr);
559
560	/* now do lines */
561	if ((ptr = x_strindex(newtc, "li#")) == NULL) {
562	    fprintf(stderr, "%s: No `li#'\n", myname);
563	    exit(EXIT_FAILURE);
564	}
565
566	i = (int) (ptr - newtc) + 3;
567	strncpy(termcap, newtc, (size_t) i);
568	sprintf(termcap + i, "%d", rows);
569	ptr = strchr(ptr, ':');
570	strcat(termcap, ptr);
571    }
572#endif /* USE_TERMCAP */
573
574    if (SHELL_BOURNE == shell_type) {
575
576#ifdef USE_TERMCAP
577	if (ok_tcap) {
578	    printf("%sTERMCAP=", setname);
579	    print_termcap(termcap);
580	    printf(";\nexport TERMCAP;\n");
581	}
582#endif /* USE_TERMCAP */
583#ifdef USE_TERMINFO
584	printf("%sCOLUMNS=%d;\nLINES=%d;\nexport COLUMNS LINES;\n",
585	       setname, cols, rows);
586#endif /* USE_TERMINFO */
587
588    } else {			/* not Bourne shell */
589
590#ifdef USE_TERMCAP
591	if (ok_tcap) {
592	    printf("set noglob;\n%ssetenv TERMCAP ", setname);
593	    print_termcap(termcap);
594	    printf(";\nunset noglob;\n");
595	}
596#endif /* USE_TERMCAP */
597#ifdef USE_TERMINFO
598	printf("set noglob;\n%ssetenv COLUMNS '%d';\nsetenv LINES '%d';\nunset noglob;\n",
599	       setname, cols, rows);
600#endif /* USE_TERMINFO */
601    }
602    exit(EXIT_SUCCESS);
603}
604