xref: /netbsd/lib/libcurses/color.c (revision c4a72b64)
1 /*	$NetBSD: color.c,v 1.21 2002/11/25 09:11:18 jdc Exp $	*/
2 
3 /*
4  * Copyright (c) 2000 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Julian Coleman.
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 NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __RCSID("$NetBSD: color.c,v 1.21 2002/11/25 09:11:18 jdc Exp $");
42 #endif				/* not lint */
43 
44 #include "curses.h"
45 #include "curses_private.h"
46 
47 /* Have we initialised colours? */
48 int	__using_color = 0;
49 
50 /* Default colour number */
51 attr_t	__default_color = 0;
52 
53 /* Default colour pair values - white on black. */
54 struct __pair	__default_pair = {COLOR_WHITE, COLOR_BLACK, 0};
55 
56 /* Default colour values */
57 /* Flags for colours and pairs */
58 #define	__USED		0x01
59 
60 /* Attributes that clash with colours */
61 attr_t	__nca;
62 
63 static void
64 __change_pair(short);
65 
66 /*
67  * has_colors --
68  *	Check if terminal has colours.
69  */
70 bool
71 has_colors(void)
72 {
73 	if (__tc_Co > 0 && __tc_pa > 0 && ((__tc_AF != NULL &&
74 	    __tc_AB != NULL) || __tc_Ip != NULL || __tc_Ic != NULL ||
75 	    (__tc_Sb != NULL && __tc_Sf != NULL)))
76 		return(TRUE);
77 	else
78 		return(FALSE);
79 }
80 
81 /*
82  * can_change_colors --
83  *	Check if terminal can change colours.
84  */
85 bool
86 can_change_colors(void)
87 {
88 	if (__tc_cc)
89 		return(TRUE);
90 	else
91 		return(FALSE);
92 }
93 
94 /*
95  * start_color --
96  *	Initialise colour support.
97  */
98 int
99 start_color(void)
100 {
101 	int			 i;
102 	attr_t			 temp_nc;
103 	struct __winlist	*wlp;
104 	WINDOW			*win;
105 	int			 y, x;
106 
107 	if (has_colors() == FALSE)
108 		return(ERR);
109 
110 	/* Max colours and colour pairs */
111 	if (__tc_Co == -1)
112 		COLORS = 0;
113 	else {
114 		COLORS = __tc_Co > MAX_COLORS ? MAX_COLORS : __tc_Co;
115 		if (__tc_pa == -1) {
116 			COLOR_PAIRS = 0;
117 			COLORS = 0;
118 		} else {
119 			COLOR_PAIRS = (__tc_pa > MAX_PAIRS ?
120 			    MAX_PAIRS : __tc_pa) - 1;
121 			 /* Use the last colour pair for curses default. */
122 			__default_color = COLOR_PAIR(COLOR_PAIRS);
123 		}
124 	}
125 	if (!COLORS)
126 		return (ERR);
127 
128 	_cursesi_screen->COLORS = COLORS;
129 	_cursesi_screen->COLOR_PAIRS = COLOR_PAIRS;
130 
131 	/* Reset terminal colour and colour pairs. */
132 	if (__tc_oc != NULL)
133 		tputs(__tc_oc, 0, __cputchar);
134 	if (__tc_op != NULL) {
135 		tputs(__tc_op, 0, __cputchar);
136 		curscr->wattr &= _cursesi_screen->mask_op;
137 	}
138 
139 	/* Type of colour manipulation - ANSI/TEK/HP/other */
140 	if (__tc_AF != NULL && __tc_AB != NULL)
141 		_cursesi_screen->color_type = COLOR_ANSI;
142 	else if (__tc_Ip != NULL)
143 		_cursesi_screen->color_type = COLOR_HP;
144 	else if (__tc_Ic != NULL)
145 		_cursesi_screen->color_type = COLOR_TEK;
146 	else if (__tc_Sb != NULL && __tc_Sf != NULL)
147 		_cursesi_screen->color_type = COLOR_OTHER;
148 	else
149 		return(ERR);		/* Unsupported colour method */
150 
151 #ifdef DEBUG
152 	__CTRACE("start_color: COLORS = %d, COLOR_PAIRS = %d",
153 	    COLORS, COLOR_PAIRS);
154 	switch (_cursesi_screen->color_type) {
155 	case COLOR_ANSI:
156 		__CTRACE(" (ANSI style)\n");
157 		break;
158 	case COLOR_HP:
159 		__CTRACE(" (HP style)\n");
160 		break;
161 	case COLOR_TEK:
162 		__CTRACE(" (Tektronics style)\n");
163 		break;
164 	case COLOR_OTHER:
165 		__CTRACE(" (Other style)\n");
166 		break;
167 	}
168 #endif
169 
170 	/*
171 	 * Attributes that cannot be used with color.
172 	 * Store these in an attr_t for wattrset()/wattron().
173 	 */
174 	_cursesi_screen->nca = __NORMAL;
175 	if (__tc_NC != -1) {
176 		temp_nc = (attr_t) t_getnum(_cursesi_screen->cursesi_genbuf, "NC");
177 		if (temp_nc & 0x0001)
178 			_cursesi_screen->nca |= __STANDOUT;
179 		if (temp_nc & 0x0002)
180 			_cursesi_screen->nca |= __UNDERSCORE;
181 		if (temp_nc & 0x0004)
182 			_cursesi_screen->nca |= __REVERSE;
183 		if (temp_nc & 0x0008)
184 			_cursesi_screen->nca |= __BLINK;
185 		if (temp_nc & 0x0010)
186 			_cursesi_screen->nca |= __DIM;
187 		if (temp_nc & 0x0020)
188 			_cursesi_screen->nca |= __BOLD;
189 		if (temp_nc & 0x0040)
190 			_cursesi_screen->nca |= __BLANK;
191 		if (temp_nc & 0x0080)
192 			_cursesi_screen->nca |= __PROTECT;
193 		if (temp_nc & 0x0100)
194 			_cursesi_screen->nca |= __ALTCHARSET;
195 	}
196 #ifdef DEBUG
197 	__CTRACE ("start_color: __nca = %d\n", _cursesi_screen->nca);
198 #endif
199 
200 	/* Set up initial 8 colours */
201 	if (COLORS >= COLOR_BLACK)
202 		(void) init_color(COLOR_BLACK, 0, 0, 0);
203 	if (COLORS >= COLOR_RED)
204 		(void) init_color(COLOR_RED, 1000, 0, 0);
205 	if (COLORS >= COLOR_GREEN)
206 		(void) init_color(COLOR_GREEN, 0, 1000, 0);
207 	if (COLORS >= COLOR_YELLOW)
208 		(void) init_color(COLOR_YELLOW, 1000, 1000, 0);
209 	if (COLORS >= COLOR_BLUE)
210 		(void) init_color(COLOR_BLUE, 0, 0, 1000);
211 	if (COLORS >= COLOR_MAGENTA)
212 		(void) init_color(COLOR_MAGENTA, 1000, 0, 1000);
213 	if (COLORS >= COLOR_CYAN)
214 		(void) init_color(COLOR_CYAN, 0, 1000, 1000);
215 	if (COLORS >= COLOR_WHITE)
216 		(void) init_color(COLOR_WHITE, 1000, 1000, 1000);
217 
218 	/* Initialise other colours */
219 	for (i = 8; i < COLORS; i++) {
220 		_cursesi_screen->colours[i].red = 0;
221 		_cursesi_screen->colours[i].green = 0;
222 		_cursesi_screen->colours[i].blue = 0;
223 		_cursesi_screen->colours[i].flags = 0;
224 	}
225 
226 	/* Initialise pair 0 to default colours. */
227 	_cursesi_screen->colour_pairs[0].fore = -1;
228 	_cursesi_screen->colour_pairs[0].back = -1;
229 	_cursesi_screen->colour_pairs[0].flags = 0;
230 
231 	/* Initialise user colour pairs to default (white on black) */
232 	for (i = 1; i < COLOR_PAIRS; i++) {
233 		_cursesi_screen->colour_pairs[i].fore = COLOR_WHITE;
234 		_cursesi_screen->colour_pairs[i].back = COLOR_BLACK;
235 		_cursesi_screen->colour_pairs[i].flags = 0;
236 	}
237 
238 	/* Initialise default colour pair. */
239 	_cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].fore =
240 	    __default_pair.fore;
241 	_cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].back =
242 	    __default_pair.back;
243 	_cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].flags =
244 	    __default_pair.flags;
245 
246 	__using_color = 1;
247 
248 	/* Set all positions on all windows to curses default colours. */
249 	for (wlp = __winlistp; wlp != NULL; wlp = wlp->nextp) {
250 		win = wlp->winp;
251 		if (wlp->winp == curscr) {
252 			/* Reset colour attribute on curscr */
253 			for (y = 0; y < curscr->maxy; y++)
254 				for (x = 0; x < curscr->maxx; x++) {
255 					if ((curscr->lines[y]->line[x].battr & __COLOR) == __default_color)
256 						curscr->lines[y]->line[x].battr &= ~__COLOR;
257 				}
258 		} else if (wlp->winp != __virtscr) {
259 			/* Set background attribute on other windows */
260 			if (!(win->battr & __COLOR))
261 				win->battr |= __default_color;
262 			for (y = 0; y < win->maxy; y++) {
263 				for (x = 0; x < win->maxx; x++)
264 					if (!(win->lines[y]->line[x].battr & __COLOR))
265 						win->lines[y]->line[x].battr |= __default_color;
266 			}
267 			__touchwin(win);
268 		}
269 	}
270 
271 	return(OK);
272 }
273 
274 /*
275  * init_pair --
276  *	Set pair foreground and background colors.
277  */
278 int
279 init_pair(short pair, short fore, short back)
280 {
281 	int	changed;
282 
283 #ifdef DEBUG
284 	__CTRACE("init_pair: %d, %d, %d\n", pair, fore, back);
285 #endif
286 
287 	if (pair < 0 || pair >= COLOR_PAIRS)
288 		return (ERR);
289 	if (fore < -1 || fore >= COLORS)
290 		return (ERR);
291 	if (back < -1 || back >= COLORS)
292 		return (ERR);
293 
294 	if ((_cursesi_screen->colour_pairs[pair].flags & __USED) &&
295 	    (fore != _cursesi_screen->colour_pairs[pair].fore ||
296 	     back != _cursesi_screen->colour_pairs[pair].back))
297 		changed = 1;
298 	else
299 		changed = 0;
300 
301 	_cursesi_screen->colour_pairs[pair].flags |= __USED;
302 	_cursesi_screen->colour_pairs[pair].fore = fore;
303 	_cursesi_screen->colour_pairs[pair].back = back;
304 
305 	/* XXX: need to initialise HP style (Ip) */
306 
307 	if (changed)
308 		__change_pair(pair);
309 	return (OK);
310 }
311 
312 /*
313  * pair_content --
314  *	Get pair foreground and background colours.
315  */
316 int
317 pair_content(short pair, short *forep, short *backp)
318 {
319 	if (pair < 0 || pair > _cursesi_screen->COLOR_PAIRS)
320 		return(ERR);
321 
322 	*forep = _cursesi_screen->colour_pairs[pair].fore;
323 	*backp = _cursesi_screen->colour_pairs[pair].back;
324 	return(OK);
325 }
326 
327 /*
328  * init_color --
329  *	Set colour red, green and blue values.
330  */
331 int
332 init_color(short color, short red, short green, short blue)
333 {
334 #ifdef DEBUG
335 	__CTRACE("init_color: %d, %d, %d, %d\n", color, red, green, blue);
336 #endif
337 	if (color < 0 || color >= _cursesi_screen->COLORS)
338 		return(ERR);
339 
340 	_cursesi_screen->colours[color].red = red;
341 	_cursesi_screen->colours[color].green = green;
342 	_cursesi_screen->colours[color].blue = blue;
343 	/* XXX Not yet implemented */
344 	return(ERR);
345 	/* XXX: need to initialise Tek style (Ic) and support HLS */
346 }
347 
348 /*
349  * color_content --
350  *	Get colour red, green and blue values.
351  */
352 int
353 color_content(short color, short *redp, short *greenp, short *bluep)
354 {
355 	if (color < 0 || color >= _cursesi_screen->COLORS)
356 		return(ERR);
357 
358 	*redp = _cursesi_screen->colours[color].red;
359 	*greenp = _cursesi_screen->colours[color].green;
360 	*bluep = _cursesi_screen->colours[color].blue;
361 	return(OK);
362 }
363 
364 /*
365  * use_default_colors --
366  *	Use terminal default colours instead of curses default colour.
367   */
368 int
369 use_default_colors()
370 {
371 #ifdef DEBUG
372 	__CTRACE("use_default_colors\n");
373 #endif
374 
375 	return(assume_default_colors(-1, -1));
376 }
377 
378 /*
379  * assume_default_colors --
380  *	Set the default foreground and background colours.
381  */
382 int
383 assume_default_colors(short fore, short back)
384 {
385 #ifdef DEBUG
386 	__CTRACE("assume_default_colors: %d, %d, %d\n", fore, back);
387 #endif
388 	__default_pair.fore = fore;
389 	__default_pair.back = back;
390 	__default_pair.flags = __USED;
391 
392 	if (COLOR_PAIRS) {
393 		_cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].fore = fore;
394 		_cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].back = back;
395 		_cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].flags = __USED;
396 	}
397 
398 	/*
399 	 * If we've already called start_color(), make sure all instances
400 	 * of the curses default colour pair are dirty.
401 	 */
402 	if (__using_color)
403 		__change_pair(PAIR_NUMBER(__default_color));
404 
405 	return(OK);
406 }
407 
408 
409 /*
410  * __set_color --
411  *	Set terminal foreground and background colours.
412  */
413 void
414 __set_color( /*ARGSUSED*/ WINDOW *win, attr_t attr)
415 {
416 	short	pair;
417 
418 	if ((curscr->wattr & __COLOR) == (attr & __COLOR))
419 		return;
420 
421 	pair = PAIR_NUMBER((u_int32_t)attr);
422 #ifdef DEBUG
423 	__CTRACE("__set_color: %d, %d, %d\n", pair,
424 		 _cursesi_screen->colour_pairs[pair].fore,
425 		 _cursesi_screen->colour_pairs[pair].back);
426 #endif
427 	switch (_cursesi_screen->color_type) {
428 	/* Set ANSI forground and background colours */
429 	case COLOR_ANSI:
430 		if (_cursesi_screen->colour_pairs[pair].fore == -1 ||
431 		    _cursesi_screen->colour_pairs[pair].back == -1)
432 			__unset_color(curscr);
433 		if (_cursesi_screen->colour_pairs[pair].fore != -1)
434 			tputs(__parse_cap(_cursesi_screen->tc_AF,
435 			    _cursesi_screen->colour_pairs[pair].fore),
436 			    0, __cputchar);
437 		if (_cursesi_screen->colour_pairs[pair].back != -1)
438 			tputs(__parse_cap(_cursesi_screen->tc_AB,
439 			    _cursesi_screen->colour_pairs[pair].back),
440 			    0, __cputchar);
441 		break;
442 	case COLOR_HP:
443 		/* XXX: need to support HP style */
444 		break;
445 	case COLOR_TEK:
446 		/* XXX: need to support Tek style */
447 		break;
448 	case COLOR_OTHER:
449 		if (_cursesi_screen->colour_pairs[pair].fore == -1 ||
450 		    _cursesi_screen->colour_pairs[pair].back == -1)
451 			__unset_color(curscr);
452 		if (_cursesi_screen->colour_pairs[pair].fore != -1)
453 			tputs(__parse_cap(_cursesi_screen->tc_Sf,
454 			    _cursesi_screen->colour_pairs[pair].fore),
455 			    0, __cputchar);
456 		if (_cursesi_screen->colour_pairs[pair].back != -1)
457 			tputs(__parse_cap(_cursesi_screen->tc_Sb,
458 			    _cursesi_screen->colour_pairs[pair].back),
459 			    0, __cputchar);
460 		break;
461 	}
462 	curscr->wattr &= ~__COLOR;
463 	curscr->wattr |= attr & __COLOR;
464 }
465 
466 /*
467  * __unset_color --
468  *	Clear terminal foreground and background colours.
469  */
470 void
471 __unset_color(WINDOW *win)
472 {
473 #ifdef DEBUG
474 	__CTRACE("__unset_color\n");
475 #endif
476 	switch (_cursesi_screen->color_type) {
477 	/* Clear ANSI forground and background colours */
478 	case COLOR_ANSI:
479 		if (__tc_op != NULL) {
480 			tputs(__tc_op, 0, __cputchar);
481 			win->wattr &= __mask_op;
482 		}
483 		break;
484 	case COLOR_HP:
485 		/* XXX: need to support HP style */
486 		break;
487 	case COLOR_TEK:
488 		/* XXX: need to support Tek style */
489 		break;
490 	case COLOR_OTHER:
491 		if (__tc_op != NULL) {
492 			tputs(__tc_op, 0, __cputchar);
493 			win->wattr &= __mask_op;
494 		}
495 		break;
496 	}
497 }
498 
499 /*
500  * __restore_colors --
501  *	Redo color definitions after restarting 'curses' mode.
502  */
503 void
504 __restore_colors(void)
505 {
506 	if (__tc_cc != NULL)
507 		switch (_cursesi_screen->color_type) {
508 		case COLOR_HP:
509 			/* XXX: need to re-initialise HP style (Ip) */
510 			break;
511 		case COLOR_TEK:
512 			/* XXX: need to re-initialise Tek style (Ic) */
513 			break;
514 		}
515 }
516 
517 /*
518  * __change_pair --
519  *	Mark dirty all positions using pair.
520  */
521 void
522 __change_pair(short pair)
523 {
524 	struct __winlist	*wlp;
525 	WINDOW			*win;
526 	int			 y, x;
527 
528 
529 	for (wlp = __winlistp; wlp != NULL; wlp = wlp->nextp) {
530 #ifdef DEBUG
531 		__CTRACE("__change_pair: win = %0.2o\n", wlp->winp);
532 #endif
533 		win = wlp->winp;
534 		if (win == curscr) {
535 			/* Reset colour attribute on curscr */
536 #ifdef DEBUG
537 			__CTRACE("__change_pair: win == curscr\n");
538 #endif
539 			for (y = 0; y < curscr->maxy; y++)
540 				for (x = 0; x < curscr->maxx; x++) {
541 					if ((curscr->lines[y]->line[x].attr &
542 					    __COLOR) == COLOR_PAIR(pair))
543 						curscr->lines[y]->line[x].attr
544 						    &= ~__COLOR;
545 					if ((curscr->lines[y]->line[x].battr &
546 					    __COLOR) == COLOR_PAIR(pair))
547 						curscr->lines[y]->line[x].battr
548 						    &= ~__COLOR;
549 				}
550 		} else if (win != __virtscr) {
551 			/* Mark dirty those positions with colour pair "pair" */
552 			for (y = 0; y < win->maxy; y++) {
553 				for (x = 0; x < win->maxx; x++)
554 					if ((win->lines[y]->line[x].attr &
555 					    __COLOR) == COLOR_PAIR(pair) ||
556 					    (win->lines[y]->line[x].battr &
557 					    __COLOR) == COLOR_PAIR(pair)) {
558 						if (!(win->lines[y]->flags &
559 						    __ISDIRTY))
560 							win->lines[y]->flags |=
561 							    __ISDIRTY;
562 						/*
563 						 * firstchp/lastchp are shared
564 						 * between parent window and
565 						 * sub-window.
566 						 */
567 						if (*win->lines[y]->firstchp >
568 						    x)
569 							*win->lines[y]->firstchp
570 							    = x;
571 						if (*win->lines[y]->lastchp < x)
572 							*win->lines[y]->lastchp
573 							    = x;
574 					}
575 #ifdef DEBUG
576 				if ((win->lines[y]->flags & __ISDIRTY))
577 					__CTRACE("__change_pair: first = %d, last = %d\n", *win->lines[y]->firstchp, *win->lines[y]->lastchp);
578 #endif
579 			}
580 		}
581 	}
582 }
583