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