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