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