screen.c revision 1.4 1 /* $NetBSD: screen.c,v 1.4 1995/04/29 01:11:36 mycroft 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 <termios.h>
53 #include <unistd.h>
54
55 #ifndef sigmask
56 #define sigmask(s) (1 << ((s) - 1))
57 #endif
58
59 #include "screen.h"
60 #include "tetris.h"
61
62 /*
63 * XXX - need a <termcap.h>
64 */
65 int tgetent __P((char *, const char *));
66 int tgetflag __P((const char *));
67 int tgetnum __P((const char *));
68 int tputs __P((const char *, int, int (*)(int)));
69
70 static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */
71 static int curscore;
72 static int isset; /* true => terminal is in game mode */
73 static struct termios oldtt;
74 static void (*tstp)();
75
76 char *tgetstr(), *tgoto();
77
78
79 /*
80 * Capabilities from TERMCAP.
81 */
82 char PC, *BC, *UP; /* tgoto requires globals: ugh! */
83 speed_t ospeed;
84
85 static char
86 *bcstr, /* backspace char */
87 *CEstr, /* clear to end of line */
88 *CLstr, /* clear screen */
89 *CMstr, /* cursor motion string */
90 #ifdef unneeded
91 *CRstr, /* "\r" equivalent */
92 #endif
93 *HOstr, /* cursor home */
94 *LLstr, /* last line, first column */
95 *pcstr, /* pad character */
96 *TEstr, /* end cursor motion mode */
97 *TIstr; /* begin cursor motion mode */
98 char
99 *SEstr, /* end standout mode */
100 *SOstr; /* begin standout mode */
101 static int
102 COnum, /* co# value */
103 LInum, /* li# value */
104 MSflag; /* can move in standout mode */
105
106
107 struct tcsinfo { /* termcap string info; some abbrevs above */
108 char tcname[3];
109 char **tcaddr;
110 } tcstrings[] = {
111 "bc", &bcstr,
112 "ce", &CEstr,
113 "cl", &CLstr,
114 "cm", &CMstr,
115 #ifdef unneeded
116 "cr", &CRstr,
117 #endif
118 "le", &BC, /* move cursor left one space */
119 "pc", &pcstr,
120 "se", &SEstr,
121 "so", &SOstr,
122 "te", &TEstr,
123 "ti", &TIstr,
124 "up", &UP, /* cursor up */
125 0
126 };
127
128 /* This is where we will actually stuff the information */
129
130 static char combuf[1024], tbuf[1024];
131
132
133 /*
134 * Routine used by tputs().
135 */
136 int
137 put(c)
138 int c;
139 {
140
141 return (putchar(c));
142 }
143
144 /*
145 * putstr() is for unpadded strings (either as in termcap(5) or
146 * simply literal strings); putpad() is for padded strings with
147 * count=1. (See screen.h for putpad().)
148 */
149 #define putstr(s) (void)fputs(s, stdout)
150 #define moveto(r, c) putpad(tgoto(CMstr, c, r))
151
152 /*
153 * Set up from termcap.
154 */
155 void
156 scr_init()
157 {
158 static int bsflag, xsflag, sgnum;
159 #ifdef unneeded
160 static int ncflag;
161 #endif
162 char *term, *fill;
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
174 }, tcnums[] = {
175 "co", &COnum,
176 "li", &LInum,
177 "sg", &sgnum,
178 0
179 };
180
181 if ((term = getenv("TERM")) == NULL)
182 stop("you must set the TERM environment variable");
183 if (tgetent(tbuf, term) <= 0)
184 stop("cannot find your termcap");
185 fill = combuf;
186 {
187 register struct tcsinfo *p;
188
189 for (p = tcstrings; p->tcaddr; p++)
190 *p->tcaddr = tgetstr(p->tcname, &fill);
191 }
192 {
193 register struct tcninfo *p;
194
195 for (p = tcflags; p->tcaddr; p++)
196 *p->tcaddr = tgetflag(p->tcname);
197 for (p = tcnums; p->tcaddr; p++)
198 *p->tcaddr = tgetnum(p->tcname);
199 }
200 if (bsflag)
201 BC = "\b";
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' */
220 static jmp_buf scr_onstop;
221
222 static void
223 stopset(sig)
224 int sig;
225 {
226 sigset_t sigset;
227
228 (void) signal(sig, SIG_DFL);
229 (void) kill(getpid(), sig);
230 sigemptyset(&sigset);
231 sigaddset(&sigset, sig);
232 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0);
233 longjmp(scr_onstop, 1);
234 }
235
236 static void
237 scr_stop(sig)
238 int sig;
239 {
240 sigset_t sigset;
241
242 scr_end();
243 (void) kill(getpid(), sig);
244 sigemptyset(&sigset);
245 sigaddset(&sigset, sig);
246 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0);
247 scr_set();
248 scr_msg(key_msg, 1);
249 }
250
251 /*
252 * Set up screen mode.
253 */
254 void
255 scr_set()
256 {
257 struct winsize ws;
258 struct termios newtt;
259 sigset_t sigset, osigset;
260 void (*ttou)();
261
262 sigemptyset(&sigset);
263 sigaddset(&sigset, SIGTSTP);
264 sigaddset(&sigset, SIGTTOU);
265 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset);
266 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN)
267 (void) signal(SIGTSTP, SIG_IGN);
268 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN)
269 (void) signal(SIGTTOU, SIG_IGN);
270 /*
271 * At last, we are ready to modify the tty state. If
272 * we stop while at it, stopset() above will longjmp back
273 * to the setjmp here and we will start over.
274 */
275 (void) setjmp(scr_onstop);
276 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
277 Rows = 0, Cols = 0;
278 if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
279 Rows = ws.ws_row;
280 Cols = ws.ws_col;
281 }
282 if (Rows == 0)
283 Rows = LInum;
284 if (Cols == 0)
285 Cols = COnum;
286 if (Rows < MINROWS || Cols < MINCOLS) {
287 (void) fprintf(stderr,
288 "the screen is too small: must be at least %d x %d",
289 MINROWS, MINCOLS);
290 stop(""); /* stop() supplies \n */
291 }
292 if (tcgetattr(0, &oldtt) < 0)
293 stop("tcgetattr() fails");
294 newtt = oldtt;
295 newtt.c_lflag &= ~(ICANON|ECHO);
296 newtt.c_oflag &= ~OXTABS;
297 if (tcsetattr(0, TCSADRAIN, &newtt) < 0)
298 stop("tcsetattr() fails");
299 ospeed = cfgetospeed(&newtt);
300 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset);
301
302 /*
303 * We made it. We are now in screen mode, modulo TIstr
304 * (which we will fix immediately).
305 */
306 if (TIstr)
307 putstr(TIstr); /* 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 */
321 void
322 scr_end()
323 {
324 sigset_t sigset, osigset;
325
326 sigemptyset(&sigset);
327 sigaddset(&sigset, SIGTSTP);
328 sigaddset(&sigset, SIGTTOU);
329 (void) sigprocmask(SIG_BLOCK, &sigset, &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 (void) fflush(stdout);
339 (void) tcsetattr(0, TCSADRAIN, &oldtt);
340 isset = 0;
341 /* restore signals */
342 (void) signal(SIGTSTP, tstp);
343 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
344 }
345
346 void
347 stop(why)
348 char *why;
349 {
350
351 if (isset)
352 scr_end();
353 (void) fprintf(stderr, "aborting: %s\n", why);
354 exit(1);
355 }
356
357 /*
358 * Clear the screen, forgetting the current contents in the process.
359 */
360 void
361 scr_clear()
362 {
363
364 putpad(CLstr);
365 curscore = -1;
366 bzero((char *)curscreen, sizeof(curscreen));
367 }
368
369 #if vax && !__GNUC__
370 typedef int regcell; /* pcc is bad at `register char', etc */
371 #else
372 typedef cell regcell;
373 #endif
374
375 /*
376 * Update the screen.
377 */
378 void
379 scr_update()
380 {
381 register cell *bp, *sp;
382 register regcell so, cur_so = 0;
383 register int i, ccol, j;
384 sigset_t sigset, osigset;
385
386 sigemptyset(&sigset);
387 sigaddset(&sigset, SIGTSTP);
388 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset);
389
390 /* always leave cursor after last displayed point */
391 curscreen[D_LAST * B_COLS - 1] = -1;
392
393 if (score != curscore) {
394 if (HOstr)
395 putpad(HOstr);
396 else
397 moveto(0, 0);
398 (void) printf("%d", score);
399 curscore = score;
400 }
401
402 bp = &board[D_FIRST * B_COLS];
403 sp = &curscreen[D_FIRST * B_COLS];
404 for (j = D_FIRST; j < D_LAST; j++) {
405 ccol = -1;
406 for (i = 0; i < B_COLS; bp++, sp++, i++) {
407 if (*sp == (so = *bp))
408 continue;
409 *sp = so;
410 if (i != ccol) {
411 if (cur_so && MSflag) {
412 putpad(SEstr);
413 cur_so = 0;
414 }
415 moveto(RTOD(j), CTOD(i));
416 }
417 if (SOstr) {
418 if (so != cur_so) {
419 putpad(so ? SOstr : SEstr);
420 cur_so = so;
421 }
422 putstr(" ");
423 } else
424 putstr(so ? "XX" : " ");
425 ccol = i + 1;
426 /*
427 * Look ahead a bit, to avoid extra motion if
428 * we will be redrawing the cell after the next.
429 * Motion probably takes four or more characters,
430 * so we save even if we rewrite two cells
431 * `unnecessarily'. Skip it all, though, if
432 * the next cell is a different color.
433 */
434 #define STOP (B_COLS - 3)
435 if (i > STOP || sp[1] != bp[1] || so != bp[1])
436 continue;
437 if (sp[2] != bp[2])
438 sp[1] = -1;
439 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) {
440 sp[2] = -1;
441 sp[1] = -1;
442 }
443 }
444 }
445 if (cur_so)
446 putpad(SEstr);
447 (void) fflush(stdout);
448 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
449 }
450
451 /*
452 * Write a message (set!=0), or clear the same message (set==0).
453 * (We need its length in case we have to overwrite with blanks.)
454 */
455 void
456 scr_msg(s, set)
457 register char *s;
458 int set;
459 {
460
461 if (set || CEstr == NULL) {
462 register int l = strlen(s);
463
464 moveto(Rows - 2, ((Cols - l) >> 1) - 1);
465 if (set)
466 putstr(s);
467 else
468 while (--l >= 0)
469 (void) putchar(' ');
470 } else {
471 moveto(Rows - 2, 0);
472 putpad(CEstr);
473 }
474 }
475