Revamp of terminal color codes
[asterisk/asterisk.git] / main / term.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2010, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Terminal Routines
22  *
23  * \author Mark Spencer <markster@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include "asterisk/_private.h"
35 #include <sys/time.h>
36 #include <signal.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39
40 #include "asterisk/term.h"
41 #include "asterisk/lock.h"
42 #include "asterisk/utils.h"
43 #include "asterisk/threadstorage.h"
44
45 static int vt100compat;
46
47 static char prepdata[80] = "";
48 static char enddata[80] = "";
49 static char quitdata[80] = "";
50
51 static const char * const termpath[] = {
52         "/usr/share/terminfo",
53         "/usr/local/share/misc/terminfo",
54         "/usr/lib/terminfo",
55         NULL
56         };
57
58 AST_THREADSTORAGE(commonbuf);
59
60 struct commonbuf {
61         short which;
62         char buffer[AST_TERM_MAX_ROTATING_BUFFERS][AST_TERM_MAX_ESCAPE_CHARS];
63 };
64
65 static int opposite(int color)
66 {
67         int lookup[] = {
68                 /* BLACK */ COLOR_BLACK,
69                 /* RED */ COLOR_MAGENTA,
70                 /* GREEN */ COLOR_GREEN,
71                 /* BROWN */ COLOR_BROWN,
72                 /* BLUE */ COLOR_CYAN,
73                 /* MAGENTA */ COLOR_RED,
74                 /* CYAN */ COLOR_BLUE,
75                 /* WHITE */ COLOR_BLACK };
76         return color ? lookup[color - 30] : 0;
77 }
78
79 /* Ripped off from Ross Ridge, but it's public domain code (libmytinfo) */
80 static short convshort(char *s)
81 {
82         register int a, b;
83
84         a = (int) s[0] & 0377;
85         b = (int) s[1] & 0377;
86
87         if (a == 0377 && b == 0377)
88                 return -1;
89         if (a == 0376 && b == 0377)
90                 return -2;
91
92         return a + b * 256;
93 }
94
95 int ast_term_init(void)
96 {
97         char *term = getenv("TERM");
98         char termfile[256] = "";
99         char buffer[512] = "";
100         int termfd = -1, parseokay = 0, i;
101
102         if (ast_opt_no_color) {
103                 return 0;
104         }
105
106         if (!ast_opt_console) {
107                 /* If any remote console is not compatible, we'll strip the color codes at that point */
108                 vt100compat = 1;
109                 goto end;
110         }
111
112         if (!term) {
113                 return 0;
114         }
115
116         for (i = 0;; i++) {
117                 if (termpath[i] == NULL) {
118                         break;
119                 }
120                 snprintf(termfile, sizeof(termfile), "%s/%c/%s", termpath[i], *term, term);
121                 termfd = open(termfile, O_RDONLY);
122                 if (termfd > -1) {
123                         break;
124                 }
125         }
126         if (termfd > -1) {
127                 int actsize = read(termfd, buffer, sizeof(buffer) - 1);
128                 short sz_names = convshort(buffer + 2);
129                 short sz_bools = convshort(buffer + 4);
130                 short n_nums   = convshort(buffer + 6);
131
132                 /* if ((sz_names + sz_bools) & 1)
133                         sz_bools++; */
134
135                 if (sz_names + sz_bools + n_nums < actsize) {
136                         /* Offset 13 is defined in /usr/include/term.h, though we do not
137                          * include it here, as it conflicts with include/asterisk/term.h */
138                         short max_colors = convshort(buffer + 12 + sz_names + sz_bools + 13 * 2);
139                         if (max_colors > 0) {
140                                 vt100compat = 1;
141                         }
142                         parseokay = 1;
143                 }
144                 close(termfd);
145         }
146
147         if (!parseokay) {
148                 /* These comparisons should not be substrings nor case-insensitive, as
149                  * terminal types are very particular about how they treat suffixes and
150                  * capitalization.  For example, terminal type 'linux-m' does NOT
151                  * support color, while 'linux' does.  Not even all vt100* terminals
152                  * support color, either (e.g. 'vt100+fnkeys'). */
153                 if (!strcmp(term, "linux")) {
154                         vt100compat = 1;
155                 } else if (!strcmp(term, "xterm")) {
156                         vt100compat = 1;
157                 } else if (!strcmp(term, "xterm-color")) {
158                         vt100compat = 1;
159                 } else if (!strcmp(term, "xterm-256color")) {
160                         vt100compat = 1;
161                 } else if (!strncmp(term, "Eterm", 5)) {
162                         /* Both entries which start with Eterm support color */
163                         vt100compat = 1;
164                 } else if (!strcmp(term, "vt100")) {
165                         vt100compat = 1;
166                 } else if (!strncmp(term, "crt", 3)) {
167                         /* Both crt terminals support color */
168                         vt100compat = 1;
169                 }
170         }
171
172 end:
173         if (vt100compat) {
174                 /* Make commands show up in nice colors */
175                 if (ast_opt_light_background) {
176                         snprintf(prepdata, sizeof(prepdata), "%c[%dm", ESC, COLOR_BROWN);
177                         snprintf(enddata, sizeof(enddata), "%c[%dm", ESC, COLOR_BLACK);
178                         snprintf(quitdata, sizeof(quitdata), "%c[0m", ESC);
179                 } else if (ast_opt_force_black_background) {
180                         snprintf(prepdata, sizeof(prepdata), "%c[%d;%d;%dm", ESC, ATTR_BRIGHT, COLOR_BROWN, COLOR_BLACK + 10);
181                         snprintf(enddata, sizeof(enddata), "%c[%d;%d;%dm", ESC, ATTR_RESET, COLOR_WHITE, COLOR_BLACK + 10);
182                         snprintf(quitdata, sizeof(quitdata), "%c[0m", ESC);
183                 } else {
184                         snprintf(prepdata, sizeof(prepdata), "%c[%d;%dm", ESC, ATTR_BRIGHT, COLOR_BROWN);
185                         snprintf(enddata, sizeof(enddata), "%c[%d;%dm", ESC, ATTR_RESET, COLOR_WHITE);
186                         snprintf(quitdata, sizeof(quitdata), "%c[0m", ESC);
187                 }
188         }
189         return 0;
190 }
191
192 char *term_color(char *outbuf, const char *inbuf, int fgcolor, int bgcolor, int maxout)
193 {
194         int attr = 0;
195
196         if (!vt100compat) {
197                 ast_copy_string(outbuf, inbuf, maxout);
198                 return outbuf;
199         }
200         if (!fgcolor) {
201                 ast_copy_string(outbuf, inbuf, maxout);
202                 return outbuf;
203         }
204
205         if (fgcolor & 128) {
206                 attr = ast_opt_light_background ? 0 : ATTR_BRIGHT;
207                 fgcolor &= ~128;
208         }
209
210         if (bgcolor) {
211                 bgcolor &= ~128;
212         }
213
214         if (ast_opt_light_background) {
215                 fgcolor = opposite(fgcolor);
216         }
217
218         if (ast_opt_force_black_background) {
219                 snprintf(outbuf, maxout, "%c[%d;%d;%dm%s%c[%d;%dm", ESC, attr, fgcolor, bgcolor + 10, inbuf, ESC, COLOR_WHITE, COLOR_BLACK + 10);
220         } else {
221                 snprintf(outbuf, maxout, "%c[%d;%dm%s%c[0m", ESC, attr, fgcolor, inbuf, ESC);
222         }
223         return outbuf;
224 }
225
226 static void check_fgcolor(int *fgcolor, int *attr)
227 {
228         *attr = ast_opt_light_background ? 0 : ATTR_BRIGHT;
229         if (*fgcolor & 128) {
230                 *fgcolor &= ~128;
231         }
232
233         if (ast_opt_light_background) {
234                 *fgcolor = opposite(*fgcolor);
235         }
236 }
237
238 static void check_bgcolor(int *bgcolor)
239 {
240         if (*bgcolor) {
241                 *bgcolor &= ~128;
242         }
243 }
244
245 static int check_colors_allowed(int fgcolor)
246 {
247         return (!vt100compat || !fgcolor) ? 0 : 1;
248 }
249
250 int ast_term_color_code(struct ast_str **str, int fgcolor, int bgcolor)
251 {
252         int attr = 0;
253
254         if (!check_colors_allowed(fgcolor)) {
255                 return -1;
256         }
257
258         check_fgcolor(&fgcolor, &attr);
259         check_bgcolor(&bgcolor);
260
261         if (ast_opt_force_black_background) {
262                 ast_str_append(str, 0, "%c[%d;%d;%dm", ESC, attr, fgcolor, COLOR_BLACK + 10);
263         } else if (bgcolor) {
264                 ast_str_append(str, 0, "%c[%d;%d;%dm", ESC, attr, fgcolor, bgcolor + 10);
265         } else {
266                 ast_str_append(str, 0, "%c[%d;%dm", ESC, attr, fgcolor);
267         }
268
269         return 0;
270 }
271
272 char *term_color_code(char *outbuf, int fgcolor, int bgcolor, int maxout)
273 {
274         int attr = 0;
275
276         if (!check_colors_allowed(fgcolor)) {
277                 *outbuf = '\0';
278                 return outbuf;
279         }
280
281         check_fgcolor(&fgcolor, &attr);
282         check_bgcolor(&bgcolor);
283
284         if (ast_opt_force_black_background) {
285                 snprintf(outbuf, maxout, "%c[%d;%d;%dm", ESC, attr, fgcolor, COLOR_BLACK + 10);
286         } else if (bgcolor) {
287                 snprintf(outbuf, maxout, "%c[%d;%d;%dm", ESC, attr, fgcolor, bgcolor + 10);
288         } else {
289                 snprintf(outbuf, maxout, "%c[%d;%dm", ESC, attr, fgcolor);
290         }
291
292         return outbuf;
293 }
294
295 const char *ast_term_color(int fgcolor, int bgcolor)
296 {
297         struct commonbuf *cb = ast_threadstorage_get(&commonbuf, sizeof(*cb));
298         char *buf;
299
300         if (!cb) {
301                 return "";
302         }
303         buf = cb->buffer[cb->which++];
304         if (cb->which == AST_TERM_MAX_ROTATING_BUFFERS) {
305                 cb->which = 0;
306         }
307
308         return term_color_code(buf, fgcolor, bgcolor, AST_TERM_MAX_ESCAPE_CHARS);
309 }
310
311 const char *ast_term_reset(void)
312 {
313         if (ast_opt_force_black_background) {
314                 static const char reset[] = { ESC, '[', COLOR_BLACK + 10, 'm', 0 };
315                 return reset;
316         } else {
317                 return quitdata;
318         }
319 }
320
321 char *term_strip(char *outbuf, const char *inbuf, int maxout)
322 {
323         char *outbuf_ptr = outbuf;
324         const char *inbuf_ptr = inbuf;
325
326         while (outbuf_ptr < outbuf + maxout) {
327                 switch (*inbuf_ptr) {
328                         case ESC:
329                                 while (*inbuf_ptr && (*inbuf_ptr != 'm'))
330                                         inbuf_ptr++;
331                                 break;
332                         default:
333                                 *outbuf_ptr = *inbuf_ptr;
334                                 outbuf_ptr++;
335                 }
336                 if (! *inbuf_ptr)
337                         break;
338                 inbuf_ptr++;
339         }
340         return outbuf;
341 }
342
343 char *term_prompt(char *outbuf, const char *inbuf, int maxout)
344 {
345         if (!vt100compat) {
346                 ast_copy_string(outbuf, inbuf, maxout);
347                 return outbuf;
348         }
349         if (ast_opt_force_black_background) {
350                 snprintf(outbuf, maxout, "%c[%d;%d;%dm%c%c[%d;%dm%s",
351                         ESC, ATTR_BRIGHT, COLOR_BLUE, COLOR_BLACK + 10,
352                         inbuf[0],
353                         ESC, COLOR_WHITE, COLOR_BLACK + 10,
354                         inbuf + 1);
355         } else if (ast_opt_light_background) {
356                 snprintf(outbuf, maxout, "%c[%d;0m%c%c[0m%s",
357                         ESC, COLOR_BLUE,
358                         inbuf[0],
359                         ESC,
360                         inbuf + 1);
361         } else {
362                 snprintf(outbuf, maxout, "%c[%d;%d;0m%c%c[0m%s",
363                         ESC, ATTR_BRIGHT, COLOR_BLUE,
364                         inbuf[0],
365                         ESC,
366                         inbuf + 1);
367         }
368         return outbuf;
369 }
370
371 /* filter escape sequences */
372 void term_filter_escapes(char *line)
373 {
374         int i;
375         int len = strlen(line);
376
377         for (i = 0; i < len; i++) {
378                 if (line[i] != ESC)
379                         continue;
380                 if ((i < (len - 2)) &&
381                     (line[i + 1] == 0x5B)) {
382                         switch (line[i + 2]) {
383                         case 0x30:
384                         case 0x31:
385                         case 0x33:
386                                 continue;
387                         }
388                 }
389                 /* replace ESC with a space */
390                 line[i] = ' ';
391         }
392 }
393
394 const char *term_prep(void)
395 {
396         return prepdata;
397 }
398
399 const char *term_end(void)
400 {
401         return enddata;
402 }
403
404 const char *term_quit(void)
405 {
406         return quitdata;
407 }