/* $XTermId: resize.c,v 1.154 2025/03/08 13:21:57 tom Exp $ */

/*
 * Copyright 2003-2024,2025 by Thomas E. Dickey
 *
 *                         All Rights Reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization.
 *
 *
 * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
 *
 *                         All Rights Reserved
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of Digital Equipment
 * Corporation not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 *
 *
 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

/* resize.c */

#include <stdio.h>
#include <ctype.h>

#include <xterm.h>
#include <version.h>
#include <xstrings.h>
#include <xtermcap.h>
#include <xterm_io.h>

#ifndef USE_TERMINFO		/* avoid conflict with configure script */
#if defined(__QNX__) || defined(__SCO__) || defined(linux) || defined(__OpenBSD__) || defined(__UNIXWARE__)
#define USE_TERMINFO
#endif
#endif

#if defined(__QNX__)
#include <unix.h>
#endif

/*
 * Some OS's may want to use both, like SCO for example.  We catch here anyone
 * who hasn't decided what they want.
 */
#if !defined(USE_TERMCAP) && !defined(USE_TERMINFO)
#define USE_TERMINFO
#endif

#include <signal.h>
#include <pwd.h>

#ifdef USE_IGNORE_RC
int ignore_unused;
#endif

#define ESCAPE(string) "\033" string

#define	EMULATIONS	2
#define	SUN		1
#define	VT100		0

#define	TIMEOUT		3

#define	SHELL_UNKNOWN	0
#define	SHELL_C		1
#define	SHELL_BOURNE	2
/* *INDENT-OFF* */
static struct {
    const char *name;
    int type;
} shell_list[] = {
    { "csh",	SHELL_C },	/* vanilla cshell */
    { "jcsh",   SHELL_C },
    { "tcsh",   SHELL_C },
    { "sh",	SHELL_BOURNE }, /* vanilla Bourne shell */
    { "ash",    SHELL_BOURNE },
    { "bash",	SHELL_BOURNE }, /* GNU Bourne again shell */
    { "dash",	SHELL_BOURNE },
    { "jsh",    SHELL_BOURNE },
    { "ksh",	SHELL_BOURNE }, /* Korn shell (from AT&T toolchest) */
    { "ksh-i",	SHELL_BOURNE }, /* another name for Korn shell */
    { "ksh93",	SHELL_BOURNE }, /* Korn shell */
    { "mksh",   SHELL_BOURNE },
    { "pdksh",  SHELL_BOURNE },
    { "zsh",    SHELL_BOURNE },
    { NULL,	SHELL_BOURNE }	/* default (same as xterm's) */
};
/* *INDENT-ON* */

static const char *const emuname[EMULATIONS] =
{
    "VT100",
    "Sun",
};
static char *myname;
static int shell_type = SHELL_UNKNOWN;
static const char *const getattr[EMULATIONS] =
{
    ESCAPE("[c"),
    NULL,
};
static const char *const getsize[EMULATIONS] =
{
    ESCAPE("7") ESCAPE("[r") ESCAPE("[9999;9999H") ESCAPE("[6n"),
    ESCAPE("[18t"),
};
#if defined(USE_STRUCT_WINSIZE)
static const char *const getwsize[EMULATIONS] =
{				/* size in pixels */
    NULL,
    ESCAPE("[14t"),
};
#endif /* USE_STRUCT_WINSIZE */
static const char *const restore[EMULATIONS] =
{
    ESCAPE("8"),
    NULL,
};
static const char *const setsize[EMULATIONS] =
{
    NULL,
    ESCAPE("[8;%s;%st"),
};

#ifdef USE_ANY_SYSV_TERMIO
static struct termio tioorig;
#elif defined(USE_TERMIOS)
static struct termios tioorig;
#else
static struct sgttyb sgorig;
#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */

static const char *const reply_attr[EMULATIONS] =
{
    ESCAPE("[?%d;%dc"),
    NULL,
};
static const char *const reply_size[EMULATIONS] =
{
    ESCAPE("[%d;%dR"),
    ESCAPE("[8;%d;%dt"),
};
static const char sunname[] = "sunsize";
static int tty;
static FILE *ttyfp;

#if defined(USE_STRUCT_WINSIZE)
static const char *wsize[EMULATIONS] =
{
    NULL,
    ESCAPE("[4;%hd;%hdt"),
};
#endif /* USE_STRUCT_WINSIZE */

static GCC_NORETURN void failed(const char *);
static GCC_NORETURN void onintr(int);
static GCC_NORETURN void resize_timeout(int);
static GCC_NORETURN void Usage(void);

static void
failed(const char *s)
{
    int save = errno;
    IGNORE_RC(write(2, myname, strlen(myname)));
    IGNORE_RC(write(2, ": ", (size_t) 2));
    errno = save;
    perror(s);
    exit(EXIT_FAILURE);
}

/* ARGSUSED */
static void
onintr(int sig GCC_UNUSED)
{
#ifdef USE_ANY_SYSV_TERMIO
    (void) ioctl(tty, TCSETAW, &tioorig);
#elif defined(USE_TERMIOS)
    (void) tcsetattr(tty, TCSADRAIN, &tioorig);
#else /* not USE_TERMIOS */
    (void) ioctl(tty, TIOCSETP, &sgorig);
#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
    exit(EXIT_FAILURE);
}

const char *timeout_message = "?";

static void
resize_timeout(int sig)
{
    fprintf(stderr, "\n%s: %s\r\n", myname, timeout_message);
    onintr(sig);
}

static void
Usage(void)
{
    fprintf(stderr, strcmp(myname, sunname) == 0 ?
	    "Usage: %s [rows cols]\n" :
	    "Usage: %s [-v] [-u] [-c] [-s [rows cols]]\n", myname);
    exit(EXIT_FAILURE);
}

#ifdef USE_TERMCAP
static void
print_termcap(const char *termcap)
{
    int ch;

    putchar('\'');
    while ((ch = *termcap++) != '\0') {
	switch (ch & 0xff) {
	case 127:		/* undo bug in GNU termcap */
	    printf("^?");
	    break;
	case '\'':		/* must escape anyway (unlikely) */
	    /* FALLTHRU */
	case '!':		/* must escape for SunOS csh */
	    putchar('\\');
	    /* FALLTHRU */
	default:
	    putchar(ch);
	    break;
	}
    }
    putchar('\'');
}
#endif /* USE_TERMCAP */

static int
checkdigits(char *str)
{
    while (*str) {
	if (!isdigit(CharOf(*str)))
	    return (0);
	str++;
    }
    return (1);
}

static void
unexpected_char(int c)
{
    fprintf(stderr,
	    "%s: unknown character %#x, exiting.\r\n",
	    myname, (unsigned) c);
    onintr(0);
}

static void
readstring(FILE *fp, char *buf, const char *str)
{
    int last, c;
#if HAVE_SETITIMER
    struct itimerval it;
#endif
    int limit = (BUFSIZ - 3);

    signal(SIGALRM, resize_timeout);
#if HAVE_SETITIMER
    memset((char *) &it, 0, sizeof(struct itimerval));
    it.it_value.tv_sec = TIMEOUT;
    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
#else
    alarm(TIMEOUT);
#endif
    if ((c = getc(fp)) == 0233) {	/* meta-escape, CSI */
	c = ESCAPE("")[0];
	*buf++ = (char) c;
	*buf++ = '[';
    } else {
	*buf++ = (char) c;
    }
    if (c != *str) {
	unexpected_char(c);
    }
    last = str[strlen(str) - 1];
    while ((c = getc(fp)) != EOF) {
	if (--limit <= 0) {
	    fprintf(stderr, "%s: unexpected response\n", myname);
	    onintr(0);
	}
	if (c < 32 || c > 126) {
	    unexpected_char(c);
	}
	*buf++ = (char) c;
	if (c == last)
	    break;
    }
#if HAVE_SETITIMER
    memset((char *) &it, 0, sizeof(struct itimerval));
    setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
#else
    alarm(0);
#endif
    *buf = 0;
}

/*
   resets termcap string to reflect current screen size
 */
int
main(int argc, char **argv ENVP_ARG)
{
#ifdef USE_TERMCAP
    char *env;
#endif
    char *ptr;
    int emu = VT100;
    char *shell;
    int i;
    int rc;
    int rows, cols;
#ifdef USE_ANY_SYSV_TERMIO
    struct termio tio;
#elif defined(USE_TERMIOS)
    struct termios tio;
#else
    struct sgttyb sg;
#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
#ifdef USE_TERMCAP
    int ok_tcap = 1;
    char termcap[TERMCAP_SIZE];
    char newtc[TERMCAP_SIZE];
#endif /* USE_TERMCAP */
    char buf[BUFSIZ];
#ifdef TTYSIZE_STRUCT
    TTYSIZE_STRUCT ts;
#endif
    char *name_of_tty;
#ifdef CANT_OPEN_DEV_TTY
    extern char *ttyname();
#endif
    const char *setname = "";

    myname = x_basename(argv[0]);
    if (strcmp(myname, sunname) == 0)
	emu = SUN;
    for (argv++, argc--; argc > 0 && **argv == '-'; argv++, argc--) {
	switch ((*argv)[1]) {
	case 's':		/* Sun emulation */
	    if (emu == SUN)
		Usage();	/* Never returns */
	    emu = SUN;
	    break;
	case 'u':		/* Bourne (Unix) shell */
	    shell_type = SHELL_BOURNE;
	    break;
	case 'c':		/* C shell */
	    shell_type = SHELL_C;
	    break;
	case 'v':
	    printf("%s\n", xtermVersion());
	    exit(EXIT_SUCCESS);
	default:
	    Usage();		/* Never returns */
	}
    }

    if (SHELL_UNKNOWN == shell_type) {
	/* Find out what kind of shell this user is running.
	 * This is the same algorithm that xterm uses.
	 */
	if ((ptr = x_getenv("SHELL")) == NULL) {
	    uid_t uid = getuid();
	    struct passwd pw;

	    if (x_getpwuid(uid, &pw)) {
		(void) x_getlogin(uid, &pw);
	    }
	    if (!OkPasswd(&pw)
		|| *(ptr = pw.pw_shell) == 0) {
		/* this is the same default that xterm uses */
		ptr = x_strdup("/bin/sh");
	    }
	}

	shell = x_basename(ptr);

	/* now that we know, what kind is it? */
	for (i = 0; shell_list[i].name; i++) {
	    if (!strcmp(shell_list[i].name, shell)) {
		break;
	    }
	}
	shell_type = shell_list[i].type;
    }

    if (argc == 2) {
	if (!setsize[emu]) {
	    fprintf(stderr,
		    "%s: Can't set window size under %s emulation\n",
		    myname, emuname[emu]);
	    exit(EXIT_FAILURE);
	}
	if (!checkdigits(argv[0]) || !checkdigits(argv[1])) {
	    Usage();		/* Never returns */
	}
    } else if (argc != 0) {
	Usage();		/* Never returns */
    }
#ifdef CANT_OPEN_DEV_TTY
    if ((name_of_tty = ttyname(fileno(stderr))) == NULL)
#endif
	name_of_tty = x_strdup("/dev/tty");

    if ((ttyfp = fopen(name_of_tty, "r+")) == NULL) {
	fprintf(stderr, "%s:  can't open terminal %s\n",
		myname, name_of_tty);
	exit(EXIT_FAILURE);
    }
    tty = fileno(ttyfp);
#ifdef USE_TERMCAP
    if ((env = x_getenv("TERM")) == NULL) {
	env = x_strdup(DFT_TERMTYPE);
	if (SHELL_BOURNE == shell_type) {
	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
	} else {
	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
	}
    }
    termcap[0] = 0;		/* ...just in case we've accidentally gotten terminfo */
    if (tgetent(termcap, env) <= 0 || termcap[0] == 0) {
	ok_tcap = 0;
    }
#endif /* USE_TERMCAP */
#ifdef USE_TERMINFO
    if (x_getenv("TERM") == NULL) {
	if (SHELL_BOURNE == shell_type) {
	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
	} else {
	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
	}
    }
#endif /* USE_TERMINFO */

#ifdef USE_ANY_SYSV_TERMIO
    rc = ioctl(tty, TCGETA, &tioorig);
    tio = tioorig;
    UIntClr(tio.c_iflag, (ICRNL | IUCLC));
    UIntClr(tio.c_lflag, (ICANON | ECHO));
    tio.c_cflag |= CS8;
    tio.c_cc[VMIN] = 6;
    tio.c_cc[VTIME] = 1;
#elif defined(USE_TERMIOS)
    rc = tcgetattr(tty, &tioorig);
    tio = tioorig;
    UIntClr(tio.c_iflag, ICRNL);
    UIntClr(tio.c_lflag, (ICANON | ECHO));
    tio.c_cflag |= CS8;
    tio.c_cc[VMIN] = 6;
    tio.c_cc[VTIME] = 1;
#else /* not USE_TERMIOS */
    rc = ioctl(tty, TIOCGETP, &sgorig);
    sg = sgorig;
    sg.sg_flags |= RAW;
    UIntClr(sg.sg_flags, ECHO);
#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
    if (rc != 0)
	failed("get tty settings");

    signal(SIGINT, onintr);
    signal(SIGQUIT, onintr);
    signal(SIGTERM, onintr);

#ifdef USE_ANY_SYSV_TERMIO
    rc = ioctl(tty, TCSETAW, &tio);
#elif defined(USE_TERMIOS)
    rc = tcsetattr(tty, TCSADRAIN, &tio);
#else /* not USE_TERMIOS */
    rc = ioctl(tty, TIOCSETP, &sg);
#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
    if (rc != 0)
	failed("set tty settings");

    if (argc == 2) {		/* look for optional parameters of "-s" */
	char *tmpbuf = TypeMallocN(char,
				   strlen(setsize[emu]) +
				   strlen(argv[0]) +
				   strlen(argv[1]) +
				   1);
	if (tmpbuf == NULL) {
	    fprintf(stderr, "%s: Cannot query size\n", myname);
	    onintr(0);
	} else {
	    sprintf(tmpbuf, setsize[emu], argv[0], argv[1]);
	    IGNORE_RC(write(tty, tmpbuf, strlen(tmpbuf)));
	    free(tmpbuf);
	}
    }
    if (getattr[emu]) {
	timeout_message = "Terminal is not VT100-compatible";
	IGNORE_RC(write(tty, getattr[emu], strlen(getattr[emu])));
	readstring(ttyfp, buf, reply_attr[emu]);
    }
    timeout_message = "Time out occurred";
    IGNORE_RC(write(tty, getsize[emu], strlen(getsize[emu])));
    readstring(ttyfp, buf, reply_size[emu]);
    if (sscanf(buf, reply_size[emu], &rows, &cols) != 2) {
	fprintf(stderr, "%s: Can't get rows and columns\r\n", myname);
	onintr(0);
    }
    if (restore[emu])
	IGNORE_RC(write(tty, restore[emu], strlen(restore[emu])));
#if defined(USE_STRUCT_WINSIZE)
    /* finally, set the tty's window size */
    if (getwsize[emu]) {
	/* get the window size in pixels */
	IGNORE_RC(write(tty, getwsize[emu], strlen(getwsize[emu])));
	readstring(ttyfp, buf, wsize[emu]);
	if (sscanf(buf, wsize[emu], &ts.ws_xpixel, &ts.ws_ypixel) != 2) {
	    fprintf(stderr, "%s: Can't get window size\r\n", myname);
	    onintr(0);
	}
	setup_winsize(ts, rows, cols, 0, 0);
	SET_TTYSIZE(tty, ts);
    } else if (ioctl(tty, TIOCGWINSZ, &ts) != -1) {
	/* we don't have any way of directly finding out
	   the current height & width of the window in pixels.  We try
	   our best by computing the font height and width from the "old"
	   window-size values, and multiplying by these ratios... */
#define scaled(old,new,len) (old)?((unsigned)(new)*(len)/(old)):(len)
	setup_winsize(ts, rows, cols,
		      scaled(TTYSIZE_ROWS(ts), rows, ts.ws_ypixel),
		      scaled(TTYSIZE_COLS(ts), cols, ts.ws_xpixel));
	SET_TTYSIZE(tty, ts);
    }
#endif /* USE_STRUCT_WINSIZE */

#ifdef USE_ANY_SYSV_TERMIO
    rc = ioctl(tty, TCSETAW, &tioorig);
#elif defined(USE_TERMIOS)
    rc = tcsetattr(tty, TCSADRAIN, &tioorig);
#else /* not USE_TERMIOS */
    rc = ioctl(tty, TIOCSETP, &sgorig);
#endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
    if (rc != 0)
	failed("set tty settings");

    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    signal(SIGTERM, SIG_DFL);

#ifdef USE_TERMCAP
    if (ok_tcap) {
	/* update termcap string */
	/* first do columns */
	if ((ptr = x_strindex(termcap, "co#")) == NULL) {
	    fprintf(stderr, "%s: No `co#'\n", myname);
	    exit(EXIT_FAILURE);
	}

	i = (int) (ptr - termcap) + 3;
	strncpy(newtc, termcap, (size_t) i);
	sprintf(newtc + i, "%d", cols);
	if ((ptr = strchr(ptr, ':')) != NULL)
	    strcat(newtc, ptr);

	/* now do lines */
	if ((ptr = x_strindex(newtc, "li#")) == NULL) {
	    fprintf(stderr, "%s: No `li#'\n", myname);
	    exit(EXIT_FAILURE);
	}

	i = (int) (ptr - newtc) + 3;
	strncpy(termcap, newtc, (size_t) i);
	sprintf(termcap + i, "%d", rows);
	if ((ptr = strchr(ptr, ':')) != NULL)
	    strcat(termcap, ptr);
    }
#endif /* USE_TERMCAP */

    if (SHELL_BOURNE == shell_type) {

#ifdef USE_TERMCAP
	if (ok_tcap) {
	    printf("%sTERMCAP=", setname);
	    print_termcap(termcap);
	    printf(";\nexport TERMCAP;\n");
	}
#endif /* USE_TERMCAP */
#ifdef USE_TERMINFO
	printf("%sCOLUMNS=%d;\nLINES=%d;\nexport COLUMNS LINES;\n",
	       setname, cols, rows);
#endif /* USE_TERMINFO */

    } else {			/* not Bourne shell */

#ifdef USE_TERMCAP
	if (ok_tcap) {
	    printf("set noglob;\n%ssetenv TERMCAP ", setname);
	    print_termcap(termcap);
	    printf(";\nunset noglob;\n");
	}
#endif /* USE_TERMCAP */
#ifdef USE_TERMINFO
	printf("set noglob;\n%ssetenv COLUMNS '%d';\nsetenv LINES '%d';\nunset noglob;\n",
	       setname, cols, rows);
#endif /* USE_TERMINFO */
    }
    exit(EXIT_SUCCESS);
}
