screen.c revision 1.22
1/* $NetBSD: screen.c,v 1.22 2008/01/28 01:38:59 dholland 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 static char backspace[] = "\b"; 178 179 if ((term = getenv("TERM")) == NULL) 180 stop("you must set the TERM environment variable"); 181 if (t_getent(&info, term) <= 0) 182 stop("cannot find your termcap"); 183 { 184 struct tcsinfo *p; 185 186 for (p = tcstrings; p->tcaddr; p++) 187 *p->tcaddr = t_agetstr(info, p->tcname); 188 } 189 { 190 struct tcninfo *p; 191 192 for (p = tcflags; p->tcaddr; p++) 193 *p->tcaddr = t_getflag(info, p->tcname); 194 for (p = tcnums; p->tcaddr; p++) 195 *p->tcaddr = t_getnum(info, p->tcname); 196 } 197 if (bsflag) 198 BC = backspace; 199 else if (BC == NULL && bcstr != NULL) 200 BC = bcstr; 201 if (CLstr == NULL) 202 stop("cannot clear screen"); 203 if (CMstr == NULL || UP == NULL || BC == NULL) 204 stop("cannot do random cursor positioning via tgoto()"); 205 PC = pcstr ? *pcstr : 0; 206 if (sgnum >= 0 || xsflag) 207 SOstr = SEstr = NULL; 208#ifdef unneeded 209 if (ncflag) 210 CRstr = NULL; 211 else if (CRstr == NULL) 212 CRstr = "\r"; 213#endif 214} 215 216/* this foolery is needed to modify tty state `atomically' */ 217static jmp_buf scr_onstop; 218 219static void 220stopset(sig) 221 int sig; 222{ 223 sigset_t set; 224 225 (void) signal(sig, SIG_DFL); 226 (void) kill(getpid(), sig); 227 sigemptyset(&set); 228 sigaddset(&set, sig); 229 (void) sigprocmask(SIG_UNBLOCK, &set, (sigset_t *)0); 230 longjmp(scr_onstop, 1); 231} 232 233static void 234scr_stop(sig) 235 int sig; 236{ 237 sigset_t set; 238 239 scr_end(); 240 (void) kill(getpid(), sig); 241 sigemptyset(&set); 242 sigaddset(&set, sig); 243 (void) sigprocmask(SIG_UNBLOCK, &set, (sigset_t *)0); 244 scr_set(); 245 scr_msg(key_msg, 1); 246} 247 248/* 249 * Set up screen mode. 250 */ 251void 252scr_set() 253{ 254 struct winsize ws; 255 struct termios newtt; 256 sigset_t nsigset, osigset; 257 void (*ttou)(int); 258 259 sigemptyset(&nsigset); 260 sigaddset(&nsigset, SIGTSTP); 261 sigaddset(&nsigset, SIGTTOU); 262 (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); 263 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) 264 (void) signal(SIGTSTP, SIG_IGN); 265 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN) 266 (void) signal(SIGTTOU, SIG_IGN); 267 /* 268 * At last, we are ready to modify the tty state. If 269 * we stop while at it, stopset() above will longjmp back 270 * to the setjmp here and we will start over. 271 */ 272 (void) setjmp(scr_onstop); 273 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 274 Rows = 0, Cols = 0; 275 if (ioctl(0, TIOCGWINSZ, &ws) == 0) { 276 Rows = ws.ws_row; 277 Cols = ws.ws_col; 278 } 279 if (Rows == 0) 280 Rows = LInum; 281 if (Cols == 0) 282 Cols = COnum; 283 if (Rows < MINROWS || Cols < MINCOLS) { 284 (void) fprintf(stderr, 285 "the screen is too small: must be at least %dx%d, ", 286 MINCOLS, MINROWS); 287 stop(""); /* stop() supplies \n */ 288 } 289 if (tcgetattr(0, &oldtt) < 0) 290 stop("tcgetattr() fails"); 291 newtt = oldtt; 292 newtt.c_lflag &= ~(ICANON|ECHO); 293 newtt.c_oflag &= ~OXTABS; 294 if (tcsetattr(0, TCSADRAIN, &newtt) < 0) 295 stop("tcsetattr() fails"); 296 ospeed = cfgetospeed(&newtt); 297 (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); 298 299 /* 300 * We made it. We are now in screen mode, modulo TIstr 301 * (which we will fix immediately). 302 */ 303 if (TIstr) 304 putstr(TIstr); /* termcap(5) says this is not padded */ 305 if (tstp != SIG_IGN) 306 (void) signal(SIGTSTP, scr_stop); 307 if (ttou != SIG_IGN) 308 (void) signal(SIGTTOU, ttou); 309 310 isset = 1; 311 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 312 scr_clear(); 313} 314 315/* 316 * End screen mode. 317 */ 318void 319scr_end() 320{ 321 sigset_t nsigset, osigset; 322 323 sigemptyset(&nsigset); 324 sigaddset(&nsigset, SIGTSTP); 325 sigaddset(&nsigset, SIGTTOU); 326 (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); 327 /* move cursor to last line */ 328 if (LLstr) 329 putstr(LLstr); /* termcap(5) says this is not padded */ 330 else 331 moveto(Rows - 1, 0); 332 /* exit screen mode */ 333 if (TEstr) 334 putstr(TEstr); /* termcap(5) says this is not padded */ 335 (void) fflush(stdout); 336 (void) tcsetattr(0, TCSADRAIN, &oldtt); 337 isset = 0; 338 /* restore signals */ 339 (void) signal(SIGTSTP, tstp); 340 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 341} 342 343void 344stop(why) 345 const char *why; 346{ 347 348 if (isset) 349 scr_end(); 350 (void) fprintf(stderr, "aborting: %s\n", why); 351 exit(1); 352} 353 354/* 355 * Clear the screen, forgetting the current contents in the process. 356 */ 357void 358scr_clear() 359{ 360 361 putpad(CLstr); 362 curscore = -1; 363 memset((char *)curscreen, 0, sizeof(curscreen)); 364} 365 366#if vax && !__GNUC__ 367typedef int regcell; /* pcc is bad at `register char', etc */ 368#else 369typedef cell regcell; 370#endif 371 372/* 373 * Update the screen. 374 */ 375void 376scr_update() 377{ 378 cell *bp, *sp; 379 regcell so, cur_so = 0; 380 int i, ccol, j; 381 sigset_t nsigset, osigset; 382 static const struct shape *lastshape; 383 384 sigemptyset(&nsigset); 385 sigaddset(&nsigset, SIGTSTP); 386 (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); 387 388 /* always leave cursor after last displayed point */ 389 curscreen[D_LAST * B_COLS - 1] = -1; 390 391 if (score != curscore) { 392 if (HOstr) 393 putpad(HOstr); 394 else 395 moveto(0, 0); 396 (void) printf("Score: %d", score); 397 curscore = score; 398 } 399 400 /* draw preview of nextpattern */ 401 if (showpreview && (nextshape != lastshape)) { 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