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