1 /* $OpenBSD: screen.c,v 1.18 2017/04/16 18:04:02 tb 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 *bcstr; /* backspace char */ 71 static char *CEstr; /* clear to end of line */ 72 static char *CLstr; /* clear screen */ 73 static char *CMstr; /* cursor motion string */ 74 #ifdef unneeded 75 static char *CRstr; /* "\r" equivalent */ 76 #endif 77 static char *HOstr; /* cursor home */ 78 static char *LLstr; /* last line, first column */ 79 static char *pcstr; /* pad character */ 80 static char *TEstr; /* end cursor motion mode */ 81 static char *TIstr; /* begin cursor motion mode */ 82 static char *VIstr; /* make cursor invisible */ 83 static char *VEstr; /* make cursor appear normal */ 84 char *SEstr; /* end standout mode */ 85 char *SOstr; /* begin standout mode */ 86 static int COnum; /* co# value */ 87 static int LInum; /* li# value */ 88 static int MSflag; /* can move in standout mode */ 89 90 91 struct tcsinfo { /* termcap string info; some abbrevs above */ 92 char tcname[3]; 93 char **tcaddr; 94 } tcstrings[] = { 95 {"bc", &bcstr}, 96 {"ce", &CEstr}, 97 {"cl", &CLstr}, 98 {"cm", &CMstr}, 99 #ifdef unneeded 100 {"cr", &CRstr}, 101 #endif 102 {"le", &BC}, /* move cursor left one space */ 103 {"pc", &pcstr}, 104 {"se", &SEstr}, 105 {"so", &SOstr}, 106 {"te", &TEstr}, 107 {"ti", &TIstr}, 108 {"vi", &VIstr}, 109 {"ve", &VEstr}, 110 {"up", &UP}, /* cursor up */ 111 { {0}, NULL} 112 }; 113 114 /* This is where we will actually stuff the information */ 115 116 static char combuf[1024], tbuf[1024]; 117 118 119 /* 120 * Routine used by tputs(). 121 */ 122 int 123 put(int c) 124 { 125 return (putchar(c)); 126 } 127 128 /* 129 * putstr() is for unpadded strings (either as in termcap(5) or 130 * simply literal strings); putpad() is for padded strings with 131 * count=1. (See screen.h for putpad().) 132 */ 133 #define putstr(s) fputs(s, stdout) 134 #define moveto(r, c) putpad(tgoto(CMstr, c, r)) 135 136 /* 137 * Set up from termcap. 138 */ 139 void 140 scr_init(void) 141 { 142 static int bsflag, xsflag, sgnum; 143 #ifdef unneeded 144 static int ncflag; 145 #endif 146 char *term, *fill; 147 static struct tcninfo { /* termcap numeric and flag info */ 148 char tcname[3]; 149 int *tcaddr; 150 } tcflags[] = { 151 {"bs", &bsflag}, 152 {"ms", &MSflag}, 153 #ifdef unneeded 154 {"nc", &ncflag}, 155 #endif 156 {"xs", &xsflag}, 157 { {0}, NULL} 158 }, tcnums[] = { 159 {"co", &COnum}, 160 {"li", &LInum}, 161 {"sg", &sgnum}, 162 { {0}, NULL} 163 }; 164 165 if ((term = getenv("TERM")) == NULL) 166 stop("you must set the TERM environment variable"); 167 if (tgetent(tbuf, term) <= 0) 168 stop("cannot find your termcap"); 169 fill = combuf; 170 { 171 struct tcsinfo *p; 172 173 for (p = tcstrings; p->tcaddr; p++) 174 *p->tcaddr = tgetstr(p->tcname, &fill); 175 } 176 if (classic) 177 SOstr = SEstr = NULL; 178 { 179 struct tcninfo *p; 180 181 for (p = tcflags; p->tcaddr; p++) 182 *p->tcaddr = tgetflag(p->tcname); 183 for (p = tcnums; p->tcaddr; p++) 184 *p->tcaddr = tgetnum(p->tcname); 185 } 186 if (bsflag) 187 BC = __DECONST(char *, "\b"); 188 else if (BC == NULL && bcstr != NULL) 189 BC = bcstr; 190 if (CLstr == NULL) 191 stop("cannot clear screen"); 192 if (CMstr == NULL || UP == NULL || BC == NULL) 193 stop("cannot do random cursor positioning via tgoto()"); 194 PC = pcstr ? *pcstr : 0; 195 if (sgnum > 0 || xsflag) 196 SOstr = SEstr = NULL; 197 #ifdef unneeded 198 if (ncflag) 199 CRstr = NULL; 200 else if (CRstr == NULL) 201 CRstr = "\r"; 202 #endif 203 } 204 205 /* this foolery is needed to modify tty state `atomically' */ 206 static jmp_buf scr_onstop; 207 208 static void 209 stopset(int sig) 210 { 211 sigset_t sigset; 212 213 signal(sig, SIG_DFL); 214 kill(getpid(), sig); 215 sigemptyset(&sigset); 216 sigaddset(&sigset, sig); 217 sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 218 longjmp(scr_onstop, 1); 219 } 220 221 static void 222 scr_stop(int sig) 223 { 224 sigset_t sigset; 225 226 scr_end(); 227 kill(getpid(), sig); 228 sigemptyset(&sigset); 229 sigaddset(&sigset, sig); 230 sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 231 scr_set(); 232 scr_msg(key_msg, 1); 233 } 234 235 /* 236 * Set up screen mode. 237 */ 238 void 239 scr_set(void) 240 { 241 struct winsize ws; 242 struct termios newtt; 243 sigset_t sigset, osigset; 244 void (*ttou)(int); 245 246 sigemptyset(&sigset); 247 sigaddset(&sigset, SIGTSTP); 248 sigaddset(&sigset, SIGTTOU); 249 sigprocmask(SIG_BLOCK, &sigset, &osigset); 250 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) 251 signal(SIGTSTP, SIG_IGN); 252 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN) 253 signal(SIGTTOU, SIG_IGN); 254 /* 255 * At last, we are ready to modify the tty state. If 256 * we stop while at it, stopset() above will longjmp back 257 * to the setjmp here and we will start over. 258 */ 259 setjmp(scr_onstop); 260 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 261 Rows = 0, Cols = 0; 262 if (ioctl(0, TIOCGWINSZ, &ws) == 0) { 263 Rows = ws.ws_row; 264 Cols = ws.ws_col; 265 } 266 if (Rows == 0) 267 Rows = LInum; 268 if (Cols == 0) 269 Cols = COnum; 270 if (Rows < MINROWS || Cols < MINCOLS) { 271 char smallscr[55]; 272 273 snprintf(smallscr, sizeof(smallscr), 274 "the screen is too small (must be at least %dx%d)", 275 MINROWS, MINCOLS); 276 stop(smallscr); 277 } 278 if (tcgetattr(0, &oldtt) < 0) 279 stop("tcgetattr() fails"); 280 newtt = oldtt; 281 newtt.c_lflag &= ~(ICANON|ECHO); 282 newtt.c_oflag &= ~OXTABS; 283 if (tcsetattr(0, TCSADRAIN, &newtt) < 0) 284 stop("tcsetattr() fails"); 285 sigprocmask(SIG_BLOCK, &sigset, &osigset); 286 287 /* 288 * We made it. We are now in screen mode, modulo TIstr 289 * (which we will fix immediately). 290 */ 291 if (TIstr) 292 putstr(TIstr); /* termcap(5) says this is not padded */ 293 if (VIstr) 294 putstr(VIstr); /* termcap(5) says this is not padded */ 295 if (tstp != SIG_IGN) 296 signal(SIGTSTP, scr_stop); 297 if (ttou != SIG_IGN) 298 signal(SIGTTOU, ttou); 299 300 isset = 1; 301 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 302 scr_clear(); 303 } 304 305 /* 306 * End screen mode. 307 */ 308 void 309 scr_end(void) 310 { 311 sigset_t sigset, osigset; 312 313 sigemptyset(&sigset); 314 sigaddset(&sigset, SIGTSTP); 315 sigaddset(&sigset, SIGTTOU); 316 sigprocmask(SIG_BLOCK, &sigset, &osigset); 317 /* move cursor to last line */ 318 if (LLstr) 319 putstr(LLstr); /* termcap(5) says this is not padded */ 320 else 321 moveto(Rows - 1, 0); 322 /* exit screen mode */ 323 if (TEstr) 324 putstr(TEstr); /* termcap(5) says this is not padded */ 325 if (VEstr) 326 putstr(VEstr); /* termcap(5) says this is not padded */ 327 fflush(stdout); 328 tcsetattr(0, TCSADRAIN, &oldtt); 329 isset = 0; 330 /* restore signals */ 331 signal(SIGTSTP, tstp); 332 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 333 } 334 335 void 336 stop(const char *why) 337 { 338 if (isset) 339 scr_end(); 340 errx(1, "aborting: %s", why); 341 } 342 343 /* 344 * Clear the screen, forgetting the current contents in the process. 345 */ 346 void 347 scr_clear(void) 348 { 349 putpad(CLstr); 350 curscore = -1; 351 memset((char *)curscreen, 0, sizeof(curscreen)); 352 } 353 354 typedef cell regcell; 355 356 /* 357 * Update the screen. 358 */ 359 void 360 scr_update(void) 361 { 362 cell *bp, *sp; 363 regcell so, cur_so = 0; 364 int i, ccol, j; 365 sigset_t sigset, osigset; 366 static const struct shape *lastshape; 367 368 sigemptyset(&sigset); 369 sigaddset(&sigset, SIGTSTP); 370 sigprocmask(SIG_BLOCK, &sigset, &osigset); 371 372 /* always leave cursor after last displayed point */ 373 curscreen[D_LAST * B_COLS - 1] = -1; 374 375 if (score != curscore) { 376 if (HOstr) 377 putpad(HOstr); 378 else 379 moveto(0, 0); 380 printf("Score: %d", score); 381 curscore = score; 382 } 383 384 /* draw preview of next pattern */ 385 if (showpreview && (nextshape != lastshape)) { 386 static int r=5, c=2; 387 int tr, tc, t; 388 389 lastshape = nextshape; 390 391 /* clean */ 392 putpad(SEstr); 393 moveto(r-1, c-1); putstr(" "); 394 moveto(r, c-1); putstr(" "); 395 moveto(r+1, c-1); putstr(" "); 396 moveto(r+2, c-1); putstr(" "); 397 398 moveto(r-3, c-2); 399 putstr("Next shape:"); 400 401 /* draw */ 402 if (SOstr) 403 putpad(SOstr); 404 moveto(r, 2 * c); 405 putstr(SOstr ? " " : "[]"); 406 for (i = 0; i < 3; i++) { 407 t = c + r * B_COLS; 408 t += nextshape->off[i]; 409 410 tr = t / B_COLS; 411 tc = t % B_COLS; 412 413 moveto(tr, 2*tc); 414 putstr(SOstr ? " " : "[]"); 415 } 416 putpad(SEstr); 417 } 418 419 bp = &board[D_FIRST * B_COLS]; 420 sp = &curscreen[D_FIRST * B_COLS]; 421 for (j = D_FIRST; j < D_LAST; j++) { 422 ccol = -1; 423 for (i = 0; i < B_COLS; bp++, sp++, i++) { 424 if (*sp == (so = *bp)) 425 continue; 426 *sp = so; 427 if (i != ccol) { 428 if (cur_so && MSflag) { 429 putpad(SEstr); 430 cur_so = 0; 431 } 432 moveto(RTOD(j), CTOD(i)); 433 } 434 if (SOstr) { 435 if (so != cur_so) { 436 putpad(so ? SOstr : SEstr); 437 cur_so = so; 438 } 439 putstr(" "); 440 } else 441 putstr(so ? "[]" : " "); 442 ccol = i + 1; 443 /* 444 * Look ahead a bit, to avoid extra motion if 445 * we will be redrawing the cell after the next. 446 * Motion probably takes four or more characters, 447 * so we save even if we rewrite two cells 448 * `unnecessarily'. Skip it all, though, if 449 * the next cell is a different color. 450 */ 451 #define STOP (B_COLS - 3) 452 if (i > STOP || sp[1] != bp[1] || so != bp[1]) 453 continue; 454 if (sp[2] != bp[2]) 455 sp[1] = -1; 456 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) { 457 sp[2] = -1; 458 sp[1] = -1; 459 } 460 } 461 } 462 if (cur_so) 463 putpad(SEstr); 464 fflush(stdout); 465 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 466 } 467 468 /* 469 * Write a message (set!=0), or clear the same message (set==0). 470 * (We need its length in case we have to overwrite with blanks.) 471 */ 472 void 473 scr_msg(char *s, int set) 474 { 475 if (set || CEstr == NULL) { 476 int l = strlen(s); 477 478 moveto(Rows - 2, ((Cols - l) >> 1) - 1); 479 if (set) 480 putstr(s); 481 else 482 while (--l >= 0) 483 putchar(' '); 484 } else { 485 moveto(Rows - 2, 0); 486 putpad(CEstr); 487 } 488 } 489