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