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 92 int 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* */ 107 static 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 129 static const char *const emuname[EMULATIONS] = 130 { 131 "VT100", 132 "Sun", 133 }; 134 static char *myname; 135 static int shell_type = SHELL_UNKNOWN; 136 static const char *const getattr[EMULATIONS] = 137 { 138 ESCAPE("[c"), 139 NULL, 140 }; 141 static 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) 147 static const char *const getwsize[EMULATIONS] = 148 { /* size in pixels */ 149 NULL, 150 ESCAPE("[14t"), 151 }; 152 #endif /* USE_STRUCT_WINSIZE */ 153 static const char *const restore[EMULATIONS] = 154 { 155 ESCAPE("8"), 156 NULL, 157 }; 158 static const char *const setsize[EMULATIONS] = 159 { 160 NULL, 161 ESCAPE("[8;%s;%st"), 162 }; 163 164 #ifdef USE_ANY_SYSV_TERMIO 165 static struct termio tioorig; 166 #elif defined(USE_TERMIOS) 167 static struct termios tioorig; 168 #else 169 static struct sgttyb sgorig; 170 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */ 171 172 static const char *const reply_attr[EMULATIONS] = 173 { 174 ESCAPE("[?%d;%dc"), 175 NULL, 176 }; 177 static const char *const reply_size[EMULATIONS] = 178 { 179 ESCAPE("[%d;%dR"), 180 ESCAPE("[8;%d;%dt"), 181 }; 182 static const char sunname[] = "sunsize"; 183 static int tty; 184 static FILE *ttyfp; 185 186 #if defined(USE_STRUCT_WINSIZE) 187 static const char *wsize[EMULATIONS] = 188 { 189 NULL, 190 ESCAPE("[4;%hd;%hdt"), 191 }; 192 #endif /* USE_STRUCT_WINSIZE */ 193 194 static GCC_NORETURN void failed(const char *); 195 static GCC_NORETURN void onintr(int); 196 static GCC_NORETURN void resize_timeout(int); 197 static GCC_NORETURN void Usage(void); 198 199 static void 200 failed(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 */ 211 static void 212 onintr(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 224 const char *timeout_message = "?"; 225 226 static void 227 resize_timeout(int sig) 228 { 229 fprintf(stderr, "\n%s: %s\r\n", myname, timeout_message); 230 onintr(sig); 231 } 232 233 static void 234 Usage(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 243 static void 244 print_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 268 static int 269 checkdigits(char *str) 270 { 271 while (*str) { 272 if (!isdigit(CharOf(*str))) 273 return (0); 274 str++; 275 } 276 return (1); 277 } 278 279 static void 280 unexpected_char(int c) 281 { 282 fprintf(stderr, 283 "%s: unknown character %#x, exiting.\r\n", 284 myname, (unsigned) c); 285 onintr(0); 286 } 287 288 static void 289 readstring(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 */ 340 int 341 main(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