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