1 /* $OpenBSD: screen.c,v 1.19 2019/06/28 13:32:52 deraadt Exp $ */ 2 /* $NetBSD: screen.c,v 1.4 1995/04/29 01:11:36 mycroft Exp $ */ 3 4 /*- 5 * Copyright (c) 1992, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Chris Torek and Darren F. Provine. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * @(#)screen.c 8.1 (Berkeley) 5/31/93 36 */ 37 38 /* 39 * Tetris screen control. 40 */ 41 42 #include <sys/ioctl.h> 43 44 #include <err.h> 45 #include <setjmp.h> 46 #include <signal.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <term.h> 51 #include <unistd.h> 52 53 #include "screen.h" 54 #include "tetris.h" 55 56 static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */ 57 static int curscore; 58 static int isset; /* true => terminal is in game mode */ 59 static struct termios oldtt; 60 static void (*tstp)(int); 61 62 static void scr_stop(int); 63 static void stopset(int); 64 65 /* 66 * Capabilities from TERMCAP. 67 */ 68 extern char PC, *BC, *UP; /* tgoto requires globals: ugh! */ 69 70 static char 71 *bcstr, /* backspace char */ 72 *CEstr, /* clear to end of line */ 73 *CLstr, /* clear screen */ 74 *CMstr, /* cursor motion string */ 75 #ifdef unneeded 76 *CRstr, /* "\r" equivalent */ 77 #endif 78 *HOstr, /* cursor home */ 79 *LLstr, /* last line, first column */ 80 *pcstr, /* pad character */ 81 *TEstr, /* end cursor motion mode */ 82 *TIstr, /* begin cursor motion mode */ 83 *VIstr, /* make cursor invisible */ 84 *VEstr; /* make cursor appear normal */ 85 char 86 *SEstr, /* end standout mode */ 87 *SOstr; /* begin standout mode */ 88 static int 89 COnum, /* co# value */ 90 LInum, /* li# value */ 91 MSflag; /* can move in standout mode */ 92 93 94 struct tcsinfo { /* termcap string info; some abbrevs above */ 95 char tcname[3]; 96 char **tcaddr; 97 } tcstrings[] = { 98 {"bc", &bcstr}, 99 {"ce", &CEstr}, 100 {"cl", &CLstr}, 101 {"cm", &CMstr}, 102 #ifdef unneeded 103 {"cr", &CRstr}, 104 #endif 105 {"le", &BC}, /* move cursor left one space */ 106 {"pc", &pcstr}, 107 {"se", &SEstr}, 108 {"so", &SOstr}, 109 {"te", &TEstr}, 110 {"ti", &TIstr}, 111 {"vi", &VIstr}, 112 {"ve", &VEstr}, 113 {"up", &UP}, /* cursor up */ 114 { {0}, NULL} 115 }; 116 117 /* This is where we will actually stuff the information */ 118 119 static char combuf[1024], tbuf[1024]; 120 121 122 /* 123 * Routine used by tputs(). 124 */ 125 int 126 put(int c) 127 { 128 129 return (putchar(c)); 130 } 131 132 /* 133 * putstr() is for unpadded strings (either as in termcap(5) or 134 * simply literal strings); putpad() is for padded strings with 135 * count=1. (See screen.h for putpad().) 136 */ 137 #define putstr(s) (void)fputs(s, stdout) 138 #define moveto(r, c) putpad(tgoto(CMstr, c, r)) 139 140 /* 141 * Set up from termcap. 142 */ 143 void 144 scr_init(void) 145 { 146 static int bsflag, xsflag, sgnum; 147 #ifdef unneeded 148 static int ncflag; 149 #endif 150 char *term, *fill; 151 static struct tcninfo { /* termcap numeric and flag info */ 152 char tcname[3]; 153 int *tcaddr; 154 } tcflags[] = { 155 {"bs", &bsflag}, 156 {"ms", &MSflag}, 157 #ifdef unneeded 158 {"nc", &ncflag}, 159 #endif 160 {"xs", &xsflag}, 161 { {0}, NULL} 162 }, tcnums[] = { 163 {"co", &COnum}, 164 {"li", &LInum}, 165 {"sg", &sgnum}, 166 { {0}, NULL} 167 }; 168 169 if ((term = getenv("TERM")) == NULL) 170 stop("you must set the TERM environment variable"); 171 if (tgetent(tbuf, term) <= 0) 172 stop("cannot find your termcap"); 173 fill = combuf; 174 { 175 struct tcsinfo *p; 176 177 for (p = tcstrings; p->tcaddr; p++) 178 *p->tcaddr = tgetstr(p->tcname, &fill); 179 } 180 if (classic) 181 SOstr = SEstr = NULL; 182 { 183 struct tcninfo *p; 184 185 for (p = tcflags; p->tcaddr; p++) 186 *p->tcaddr = tgetflag(p->tcname); 187 for (p = tcnums; p->tcaddr; p++) 188 *p->tcaddr = tgetnum(p->tcname); 189 } 190 if (bsflag) 191 BC = "\b"; 192 else if (BC == NULL && bcstr != NULL) 193 BC = bcstr; 194 if (CLstr == NULL) 195 stop("cannot clear screen"); 196 if (CMstr == NULL || UP == NULL || BC == NULL) 197 stop("cannot do random cursor positioning via tgoto()"); 198 PC = pcstr ? *pcstr : 0; 199 if (sgnum > 0 || xsflag) 200 SOstr = SEstr = NULL; 201 #ifdef unneeded 202 if (ncflag) 203 CRstr = NULL; 204 else if (CRstr == NULL) 205 CRstr = "\r"; 206 #endif 207 } 208 209 /* this foolery is needed to modify tty state `atomically' */ 210 static jmp_buf scr_onstop; 211 212 static void 213 stopset(int sig) 214 { 215 sigset_t sigset; 216 217 (void) signal(sig, SIG_DFL); 218 (void) kill(getpid(), sig); 219 sigemptyset(&sigset); 220 sigaddset(&sigset, sig); 221 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 222 longjmp(scr_onstop, 1); 223 } 224 225 static void 226 scr_stop(int sig) 227 { 228 sigset_t sigset; 229 230 scr_end(); 231 (void) kill(getpid(), sig); 232 sigemptyset(&sigset); 233 sigaddset(&sigset, sig); 234 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 235 scr_set(); 236 scr_msg(key_msg, 1); 237 } 238 239 /* 240 * Set up screen mode. 241 */ 242 void 243 scr_set(void) 244 { 245 struct winsize ws; 246 struct termios newtt; 247 sigset_t sigset, osigset; 248 void (*ttou)(int); 249 250 sigemptyset(&sigset); 251 sigaddset(&sigset, SIGTSTP); 252 sigaddset(&sigset, SIGTTOU); 253 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 254 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) 255 (void) signal(SIGTSTP, SIG_IGN); 256 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN) 257 (void) signal(SIGTTOU, SIG_IGN); 258 /* 259 * At last, we are ready to modify the tty state. If 260 * we stop while at it, stopset() above will longjmp back 261 * to the setjmp here and we will start over. 262 */ 263 (void) setjmp(scr_onstop); 264 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 265 Rows = 0, Cols = 0; 266 if (ioctl(0, TIOCGWINSZ, &ws) == 0) { 267 Rows = ws.ws_row; 268 Cols = ws.ws_col; 269 } 270 if (Rows == 0) 271 Rows = LInum; 272 if (Cols == 0) 273 Cols = COnum; 274 if (Rows < MINROWS || Cols < MINCOLS) { 275 char smallscr[55]; 276 277 (void)snprintf(smallscr, sizeof(smallscr), 278 "the screen is too small (must be at least %dx%d)", 279 MINROWS, MINCOLS); 280 stop(smallscr); 281 } 282 if (tcgetattr(0, &oldtt) == -1) 283 stop("tcgetattr() fails"); 284 newtt = oldtt; 285 newtt.c_lflag &= ~(ICANON|ECHO); 286 newtt.c_oflag &= ~OXTABS; 287 if (tcsetattr(0, TCSADRAIN, &newtt) == -1) 288 stop("tcsetattr() fails"); 289 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 290 291 /* 292 * We made it. We are now in screen mode, modulo TIstr 293 * (which we will fix immediately). 294 */ 295 if (TIstr) 296 putstr(TIstr); /* termcap(5) says this is not padded */ 297 if (VIstr) 298 putstr(VIstr); /* termcap(5) says this is not padded */ 299 if (tstp != SIG_IGN) 300 (void) signal(SIGTSTP, scr_stop); 301 if (ttou != SIG_IGN) 302 (void) signal(SIGTTOU, ttou); 303 304 isset = 1; 305 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 306 scr_clear(); 307 } 308 309 /* 310 * End screen mode. 311 */ 312 void 313 scr_end(void) 314 { 315 sigset_t sigset, osigset; 316 317 sigemptyset(&sigset); 318 sigaddset(&sigset, SIGTSTP); 319 sigaddset(&sigset, SIGTTOU); 320 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 321 /* move cursor to last line */ 322 if (LLstr) 323 putstr(LLstr); /* termcap(5) says this is not padded */ 324 else 325 moveto(Rows - 1, 0); 326 /* exit screen mode */ 327 if (TEstr) 328 putstr(TEstr); /* termcap(5) says this is not padded */ 329 if (VEstr) 330 putstr(VEstr); /* termcap(5) says this is not padded */ 331 (void) fflush(stdout); 332 (void) tcsetattr(0, TCSADRAIN, &oldtt); 333 isset = 0; 334 /* restore signals */ 335 (void) signal(SIGTSTP, tstp); 336 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 337 } 338 339 void 340 stop(char *why) 341 { 342 343 if (isset) 344 scr_end(); 345 errx(1, "aborting: %s", why); 346 } 347 348 /* 349 * Clear the screen, forgetting the current contents in the process. 350 */ 351 void 352 scr_clear(void) 353 { 354 355 putpad(CLstr); 356 curscore = -1; 357 memset((char *)curscreen, 0, sizeof(curscreen)); 358 } 359 360 typedef cell regcell; 361 362 /* 363 * Update the screen. 364 */ 365 void 366 scr_update(void) 367 { 368 cell *bp, *sp; 369 regcell so, cur_so = 0; 370 int i, ccol, j; 371 sigset_t sigset, osigset; 372 static const struct shape *lastshape; 373 374 sigemptyset(&sigset); 375 sigaddset(&sigset, SIGTSTP); 376 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 377 378 /* always leave cursor after last displayed point */ 379 curscreen[D_LAST * B_COLS - 1] = -1; 380 381 if (score != curscore) { 382 if (HOstr) 383 putpad(HOstr); 384 else 385 moveto(0, 0); 386 (void) printf("Score: %d", score); 387 curscore = score; 388 } 389 390 /* draw preview of next pattern */ 391 if (showpreview && (nextshape != lastshape)) { 392 static int r=5, c=2; 393 int tr, tc, t; 394 395 lastshape = nextshape; 396 397 /* clean */ 398 putpad(SEstr); 399 moveto(r-1, c-1); putstr(" "); 400 moveto(r, c-1); putstr(" "); 401 moveto(r+1, c-1); putstr(" "); 402 moveto(r+2, c-1); putstr(" "); 403 404 moveto(r-3, c-2); 405 putstr("Next shape:"); 406 407 /* draw */ 408 if (SOstr) 409 putpad(SOstr); 410 moveto(r, 2 * c); 411 putstr(SOstr ? " " : "[]"); 412 for (i = 0; i < 3; i++) { 413 t = c + r * B_COLS; 414 t += nextshape->off[i]; 415 416 tr = t / B_COLS; 417 tc = t % B_COLS; 418 419 moveto(tr, 2*tc); 420 putstr(SOstr ? " " : "[]"); 421 } 422 putpad(SEstr); 423 } 424 425 bp = &board[D_FIRST * B_COLS]; 426 sp = &curscreen[D_FIRST * B_COLS]; 427 for (j = D_FIRST; j < D_LAST; j++) { 428 ccol = -1; 429 for (i = 0; i < B_COLS; bp++, sp++, i++) { 430 if (*sp == (so = *bp)) 431 continue; 432 *sp = so; 433 if (i != ccol) { 434 if (cur_so && MSflag) { 435 putpad(SEstr); 436 cur_so = 0; 437 } 438 moveto(RTOD(j), CTOD(i)); 439 } 440 if (SOstr) { 441 if (so != cur_so) { 442 putpad(so ? SOstr : SEstr); 443 cur_so = so; 444 } 445 putstr(" "); 446 } else 447 putstr(so ? "[]" : " "); 448 ccol = i + 1; 449 /* 450 * Look ahead a bit, to avoid extra motion if 451 * we will be redrawing the cell after the next. 452 * Motion probably takes four or more characters, 453 * so we save even if we rewrite two cells 454 * `unnecessarily'. Skip it all, though, if 455 * the next cell is a different color. 456 */ 457 #define STOP (B_COLS - 3) 458 if (i > STOP || sp[1] != bp[1] || so != bp[1]) 459 continue; 460 if (sp[2] != bp[2]) 461 sp[1] = -1; 462 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) { 463 sp[2] = -1; 464 sp[1] = -1; 465 } 466 } 467 } 468 if (cur_so) 469 putpad(SEstr); 470 (void) fflush(stdout); 471 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 472 } 473 474 /* 475 * Write a message (set!=0), or clear the same message (set==0). 476 * (We need its length in case we have to overwrite with blanks.) 477 */ 478 void 479 scr_msg(char *s, int set) 480 { 481 482 if (set || CEstr == NULL) { 483 int l = strlen(s); 484 485 moveto(Rows - 2, ((Cols - l) >> 1) - 1); 486 if (set) 487 putstr(s); 488 else 489 while (--l >= 0) 490 (void) putchar(' '); 491 } else { 492 moveto(Rows - 2, 0); 493 putpad(CEstr); 494 } 495 } 496