Merged 0.7.99 irssi.
[crypto.git] / apps / irssi / src / fe-text / terminfo-core.c
1 #include "module.h"
2 #include "signals.h"
3 #include "terminfo-core.h"
4
5 #ifndef _POSIX_VDISABLE
6 #  define _POSIX_VDISABLE 0
7 #endif
8
9 #define tput(s) tputs(s, 0, term_putchar)
10 inline static int term_putchar(int c)
11 {
12         return fputc(c, current_term->out);
13 }
14
15 /* Don't bother including curses.h because of these -
16    they might not even be defined there */
17 char *tparm();
18 int tputs();
19
20 #ifdef HAVE_TERMINFO
21 int setupterm();
22 char *tigetstr();
23 int tigetnum();
24 int tigetflag();
25 #define term_getstr(x, buffer) tigetstr(x.ti_name)
26 #define term_getnum(x) tigetnum(x.ti_name);
27 #define term_getflag(x) tigetflag(x.ti_name);
28 #else
29 int tgetent();
30 char *tgetstr();
31 int tgetnum();
32 int tgetflag();
33 #define term_getstr(x, buffer) tgetstr(x.tc_name, &buffer)
34 #define term_getnum(x) tgetnum(x.tc_name)
35 #define term_getflag(x) tgetflag(x.tc_name)
36 #endif
37
38 #define CAP_TYPE_FLAG   0
39 #define CAP_TYPE_INT    1
40 #define CAP_TYPE_STR    2
41
42 typedef struct {
43         const char *ti_name; /* terminfo name */
44         const char *tc_name; /* termcap name */
45         int type;
46         void *ptr;
47 } TERMINFO_REC;
48
49 TERM_REC *current_term;
50 static TERM_REC temp_term; /* not really used for anything */
51
52 /* Define only what we might need */
53 static TERMINFO_REC tcaps[] = {
54         /* Terminal size */
55         { "cols",       "co",   CAP_TYPE_INT,   &temp_term.width },
56         { "lines",      "li",   CAP_TYPE_INT,   &temp_term.height },
57
58         /* Cursor movement */
59         { "smcup",      "ti",   CAP_TYPE_STR,   &temp_term.TI_smcup },
60         { "rmcup",      "te",   CAP_TYPE_STR,   &temp_term.TI_rmcup },
61         { "cup",        "cm",   CAP_TYPE_STR,   &temp_term.TI_cup },
62         { "hpa",        "ch",   CAP_TYPE_STR,   &temp_term.TI_hpa },
63         { "vpa",        "vh",   CAP_TYPE_STR,   &temp_term.TI_vpa },
64         { "cub1",       "le",   CAP_TYPE_STR,   &temp_term.TI_cub1 },
65         { "cuf1",       "nd",   CAP_TYPE_STR,   &temp_term.TI_cuf1 },
66         { "civis",      "vi",   CAP_TYPE_STR,   &temp_term.TI_civis },
67         { "cnorm",      "ve",   CAP_TYPE_STR,   &temp_term.TI_cnorm },
68
69         /* Scrolling */
70         { "csr",        "cs",   CAP_TYPE_STR,   &temp_term.TI_csr },
71         { "wind",       "wi",   CAP_TYPE_STR,   &temp_term.TI_wind },
72         { "ri",         "sr",   CAP_TYPE_STR,   &temp_term.TI_ri },
73         { "rin",        "SR",   CAP_TYPE_STR,   &temp_term.TI_rin },
74         { "ind",        "sf",   CAP_TYPE_STR,   &temp_term.TI_ind },
75         { "indn",       "SF",   CAP_TYPE_STR,   &temp_term.TI_indn },
76         { "il",         "AL",   CAP_TYPE_STR,   &temp_term.TI_il },
77         { "il1",        "al",   CAP_TYPE_STR,   &temp_term.TI_il1 },
78         { "dl",         "DL",   CAP_TYPE_STR,   &temp_term.TI_dl },
79         { "dl1",        "dl",   CAP_TYPE_STR,   &temp_term.TI_dl1 },
80
81         /* Clearing screen */
82         { "clear",      "cl",   CAP_TYPE_STR,   &temp_term.TI_clear },
83         { "ed",         "cd",   CAP_TYPE_STR,   &temp_term.TI_ed },
84
85         /* Clearing to end of line */
86         { "el",         "ce",   CAP_TYPE_STR,   &temp_term.TI_el },
87
88         /* Repeating character */
89         { "rep",        "rp",   CAP_TYPE_STR,   &temp_term.TI_rep },
90
91         /* Colors */
92         { "sgr0",       "me",   CAP_TYPE_STR,   &temp_term.TI_sgr0 },
93         { "smul",       "us",   CAP_TYPE_STR,   &temp_term.TI_smul },
94         { "rmul",       "ue",   CAP_TYPE_STR,   &temp_term.TI_rmul },
95         { "smso",       "so",   CAP_TYPE_STR,   &temp_term.TI_smso },
96         { "rmso",       "se",   CAP_TYPE_STR,   &temp_term.TI_rmso },
97         { "bold",       "md",   CAP_TYPE_STR,   &temp_term.TI_bold },
98         { "blink",      "mb",   CAP_TYPE_STR,   &temp_term.TI_blink },
99         { "setaf",      "AF",   CAP_TYPE_STR,   &temp_term.TI_setaf },
100         { "setab",      "AB",   CAP_TYPE_STR,   &temp_term.TI_setab },
101         { "setf",       "Sf",   CAP_TYPE_STR,   &temp_term.TI_setf },
102         { "setb",       "Sb",   CAP_TYPE_STR,   &temp_term.TI_setb },
103
104         /* Beep */
105         { "bel",        "bl",   CAP_TYPE_STR,   &temp_term.TI_bel },
106 };
107
108 /* Move cursor (cursor_address / cup) */
109 static void _move_cup(TERM_REC *term, int x, int y)
110 {
111         tput(tparm(term->TI_cup, y, x));
112 }
113
114 /* Move cursor (column_address+row_address / hpa+vpa) */
115 static void _move_pa(TERM_REC *term, int x, int y)
116 {
117         tput(tparm(term->TI_hpa, x));
118         tput(tparm(term->TI_vpa, y));
119 }
120
121 /* Move cursor from a known position */
122 static void _move_relative(TERM_REC *term, int oldx, int oldy, int x, int y)
123 {
124         if (oldx == 0 && x == 0 && y == oldy+1) {
125                 /* move to beginning of next line -
126                    hope this works everywhere */
127                 tput("\r\n");
128                 return;
129         }
130
131         if (oldx > 0 && y == oldy) {
132                 /* move cursor left/right */
133                 if (x == oldx-1 && term->TI_cub1) {
134                         tput(tparm(term->TI_cub1));
135                         return;
136                 }
137                 if (x == oldx+1 && y == oldy && term->TI_cuf1) {
138                         tput(tparm(term->TI_cuf1));
139                         return;
140                 }
141         }
142
143         /* fallback to absolute positioning */
144         if (term->TI_cup) {
145                 tput(tparm(term->TI_cup, y, x));
146                 return;
147         }
148
149         if (oldy != y)
150                 tput(tparm(term->TI_vpa, y));
151         if (oldx != x)
152                 tput(tparm(term->TI_hpa, x));
153 }
154
155 /* Set cursor visible/invisible */
156 static void _set_cursor_visible(TERM_REC *term, int set)
157 {
158         tput(tparm(set ? term->TI_cnorm : term->TI_civis));
159 }
160
161 #define scroll_region_setup(term, y1, y2) \
162         if ((term)->TI_csr != NULL) \
163                 tput(tparm((term)->TI_csr, y1, y2)); \
164         else if ((term)->TI_wind != NULL) \
165                 tput(tparm((term)->TI_wind, y1, y2, 0, (term)->width-1));
166
167 /* Scroll (change_scroll_region+parm_rindex+parm_index / csr+rin+indn) */
168 static void _scroll_region(TERM_REC *term, int y1, int y2, int count)
169 {
170         /* setup the scrolling region to wanted area */
171         scroll_region_setup(term, y1, y2);
172
173         term->move(term, 0, y1);
174         if (count > 0) {
175                 term->move(term, 0, y2);
176                 tput(tparm(term->TI_indn, count, count));
177         } else if (count < 0) {
178                 term->move(term, 0, y1);
179                 tput(tparm(term->TI_rin, -count, -count));
180         }
181
182         /* reset the scrolling region to full screen */
183         scroll_region_setup(term, 0, term->height-1);
184 }
185
186 /* Scroll (change_scroll_region+scroll_reverse+scroll_forward / csr+ri+ind) */
187 static void _scroll_region_1(TERM_REC *term, int y1, int y2, int count)
188 {
189         int i;
190
191         /* setup the scrolling region to wanted area */
192         scroll_region_setup(term, y1, y2);
193
194         if (count > 0) {
195                 term->move(term, 0, y2);
196                 for (i = 0; i < count; i++)
197                         tput(tparm(term->TI_ind));
198         } else if (count < 0) {
199                 term->move(term, 0, y1);
200                 for (i = count; i < 0; i++)
201                         tput(tparm(term->TI_ri));
202                 tput(tparm(term->TI_rin, -count, -count));
203         }
204
205         /* reset the scrolling region to full screen */
206         scroll_region_setup(term, 0, term->height-1);
207 }
208
209 /* Scroll (parm_insert_line+parm_delete_line / il+dl) */
210 static void _scroll_line(TERM_REC *term, int y1, int y2, int count)
211 {
212         /* setup the scrolling region to wanted area -
213            this might not necessarily work with il/dl, but at least it
214            looks better if it does */
215         scroll_region_setup(term, y1, y2);
216
217         if (count > 0) {
218                 term->move(term, 0, y1);
219                 tput(tparm(term->TI_dl, count, count));
220                 term->move(term, 0, y2-count+1);
221                 tput(tparm(term->TI_il, count, count));
222         } else if (count < 0) {
223                 term->move(term, 0, y2+count+1);
224                 tput(tparm(term->TI_dl, -count, -count));
225                 term->move(term, 0, y1);
226                 tput(tparm(term->TI_il, -count, -count));
227         }
228
229         /* reset the scrolling region to full screen */
230         scroll_region_setup(term, 0, term->height-1);
231 }
232
233 /* Scroll (insert_line+delete_line / il1+dl1) */
234 static void _scroll_line_1(TERM_REC *term, int y1, int y2, int count)
235 {
236         int i;
237
238         if (count > 0) {
239                 term->move(term, 0, y1);
240                 for (i = 0; i < count; i++)
241                         tput(tparm(term->TI_dl1));
242                 term->move(term, 0, y2-count+1);
243                 for (i = 0; i < count; i++)
244                         tput(tparm(term->TI_il1));
245         } else if (count < 0) {
246                 term->move(term, 0, y2+count+1);
247                 for (i = count; i < 0; i++)
248                         tput(tparm(term->TI_dl1));
249                 term->move(term, 0, y1);
250                 for (i = count; i < 0; i++)
251                         tput(tparm(term->TI_il1));
252         }
253 }
254
255 /* Clear screen (clear_screen / clear) */
256 static void _clear_screen(TERM_REC *term)
257 {
258         tput(tparm(term->TI_clear));
259 }
260
261 /* Clear screen (clr_eos / ed) */
262 static void _clear_eos(TERM_REC *term)
263 {
264         term->move(term, 0, 0);
265         tput(tparm(term->TI_ed));
266 }
267
268 /* Clear screen (parm_delete_line / dl) */
269 static void _clear_del(TERM_REC *term)
270 {
271         term->move(term, 0, 0);
272         tput(tparm(term->TI_dl, term->height, term->height));
273 }
274
275 /* Clear screen (delete_line / dl1) */
276 static void _clear_del_1(TERM_REC *term)
277 {
278         int i;
279
280         term->move(term, 0, 0);
281         for (i = 0; i < term->height; i++)
282                 tput(tparm(term->TI_dl1));
283 }
284
285 /* Clear to end of line (clr_eol / el) */
286 static void _clrtoeol(TERM_REC *term)
287 {
288         tput(tparm(term->TI_el));
289 }
290
291 /* Repeat character (rep / rp) */
292 static void _repeat(TERM_REC *term, int chr, int count)
293 {
294         tput(tparm(term->TI_rep, chr, count));
295 }
296
297 /* Repeat character (manual) */
298 static void _repeat_manual(TERM_REC *term, int chr, int count)
299 {
300         while (count > 0) {
301                 putc(chr, term->out);
302                 count--;
303         }
304 }
305
306 /* Reset all terminal attributes */
307 static void _set_normal(TERM_REC *term)
308 {
309         tput(tparm(term->TI_normal));
310 }
311
312 /* Bold on */
313 static void _set_bold(TERM_REC *term)
314 {
315         tput(tparm(term->TI_bold));
316 }
317
318 /* Underline on/off */
319 static void _set_uline(TERM_REC *term, int set)
320 {
321         tput(tparm(set ? term->TI_smul : term->TI_rmul));
322 }
323
324 /* Standout on/off */
325 static void _set_standout(TERM_REC *term, int set)
326 {
327         tput(tparm(set ? term->TI_smso : term->TI_rmso));
328 }
329
330 /* Change foreground color */
331 static void _set_fg(TERM_REC *term, int color)
332 {
333         tput(tparm(term->TI_fg[color & 0x0f]));
334 }
335
336 /* Change background color */
337 static void _set_bg(TERM_REC *term, int color)
338 {
339         tput(tparm(term->TI_bg[color & 0x0f]));
340 }
341
342 /* Beep */
343 static void _beep(TERM_REC *term)
344 {
345         tput(tparm(term->TI_bel));
346 }
347
348 static void _ignore(TERM_REC *term)
349 {
350 }
351
352 static void _ignore_parm(TERM_REC *term, int param)
353 {
354 }
355
356 static void term_fill_capabilities(TERM_REC *term)
357 {
358         int i, ival;
359         char *sval;
360         void *ptr;
361
362 #ifndef HAVE_TERMINFO
363         char *tptr = term->buffer2;
364 #endif
365         for (i = 0; i < sizeof(tcaps)/sizeof(tcaps[0]); i++) {
366                 ptr = (char *) term + (int) ((char *) tcaps[i].ptr - (char *) &temp_term);
367
368                 switch (tcaps[i].type) {
369                 case CAP_TYPE_FLAG:
370                         ival = term_getflag(tcaps[i]);
371                         *(int *)ptr = ival;
372                         break;
373                 case CAP_TYPE_INT:
374                         ival = term_getnum(tcaps[i]);
375                         *(int *)ptr = ival;
376                         break;
377                 case CAP_TYPE_STR:
378                         sval = term_getstr(tcaps[i], tptr);
379                         if (sval == (char *) -1)
380                                 *(char **)ptr = NULL;
381                         else
382                                 *(char **)ptr = sval;
383                         break;
384                 }
385         }
386 }
387
388 /* Terminal was resized - ask the width/height from terminfo again */
389 void terminfo_resize(TERM_REC *term)
390 {
391         /* FIXME: is this possible? */
392 }
393
394 static void terminfo_colors_deinit(TERM_REC *term)
395 {
396         int i;
397
398         if (terminfo_is_colors_set(term)) {
399                 for (i = 0; i < 16; i++) {
400                         g_free(term->TI_fg[i]);
401                         g_free(term->TI_bg[i]);
402                 }
403
404                 memset(term->TI_fg, 0, sizeof(term->TI_fg));
405                 memset(term->TI_bg, 0, sizeof(term->TI_fg));
406         }
407 }
408
409 /* Setup colors - if force is set, use ANSI-style colors if
410    terminal capabilities don't contain color codes */
411 void terminfo_setup_colors(TERM_REC *term, int force)
412 {
413         static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
414         const char *bold, *blink;
415         int i;
416
417         terminfo_colors_deinit(term);
418         term->has_colors = term->TI_setf || term->TI_setaf;
419
420         if (term->TI_setf) {
421                 for (i = 0; i < 8; i++)
422                         term->TI_fg[i] = g_strdup(tparm(term->TI_setf, i, 0));
423         } else if (term->TI_setaf) {
424                 for (i = 0; i < 8; i++)
425                         term->TI_fg[i] = g_strdup(tparm(term->TI_setaf, ansitab[i], 0));
426         } else if (force) {
427                 for (i = 0; i < 8; i++)
428                         term->TI_fg[i] = g_strdup_printf("\033[%dm", 30+ansitab[i]);
429         }
430
431         if (term->TI_setb) {
432                 for (i = 0; i < 8; i++)
433                         term->TI_bg[i] = g_strdup(tparm(term->TI_setb, i, 0));
434         } else if (term->TI_setab) {
435                 for (i = 0; i < 8; i++)
436                         term->TI_bg[i] = g_strdup(tparm(term->TI_setab, ansitab[i], 0));
437         } else if (force) {
438                 for (i = 0; i < 8; i++)
439                         term->TI_bg[i] = g_strdup_printf("\033[%dm", 40+ansitab[i]);
440         }
441
442         if (term->TI_setf || term->TI_setaf || force) {
443                 term->set_fg = _set_fg;
444                 term->set_bg = _set_bg;
445
446                 /* bold fg, blink bg */
447                 bold = term->TI_bold ? term->TI_bold : "";
448                 for (i = 0; i < 8; i++)
449                         term->TI_fg[i+8] = g_strconcat(bold, term->TI_fg[i], NULL);
450
451                 blink = term->TI_blink ? term->TI_blink : "";
452                 for (i = 0; i < 8; i++)
453                         term->TI_bg[i+8] = g_strconcat(blink, term->TI_bg[i], NULL);
454         } else {
455                 /* no colors */
456                 term->set_fg = term->set_bg = _ignore_parm;
457         }
458 }
459
460 static void terminfo_input_init(TERM_REC *term)
461 {
462         tcgetattr(fileno(term->in), &term->old_tio);
463         memcpy(&term->tio, &term->old_tio, sizeof(term->tio));
464
465         term->tio.c_lflag &= ~(ICANON | ECHO); /* CBREAK, no ECHO */
466         term->tio.c_cc[VMIN] = 1; /* read() is satisfied after 1 char */
467         term->tio.c_cc[VTIME] = 0; /* No timer */
468
469         /* Disable INTR, QUIT, VDSUSP and SUSP keys */
470         term->tio.c_cc[VINTR] = _POSIX_VDISABLE;
471         term->tio.c_cc[VQUIT] = _POSIX_VDISABLE;
472 #ifdef VDSUSP
473         term->tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
474 #endif
475 #ifdef VSUSP
476         term->tio.c_cc[VSUSP] = _POSIX_VDISABLE;
477 #endif
478
479         tcsetattr(fileno(term->in), TCSADRAIN, &term->tio);
480
481 }
482
483 static void terminfo_input_deinit(TERM_REC *term)
484 {
485         tcsetattr(fileno(term->in), TCSADRAIN, &term->old_tio);
486 }
487
488 void terminfo_cont(TERM_REC *term)
489 {
490         if (term->TI_smcup)
491                 tput(tparm(term->TI_smcup));
492         terminfo_input_init(term);
493 }
494
495 void terminfo_stop(TERM_REC *term)
496 {
497         /* reset colors */
498         terminfo_set_normal();
499         /* move cursor to bottom of the screen */
500         terminfo_move(0, term->height-1);
501
502         /* stop cup-mode */
503         if (term->TI_rmcup)
504                 tput(tparm(term->TI_rmcup));
505
506         /* reset input settings */
507         terminfo_input_deinit(term);
508         fflush(term->out);
509 }
510
511 static int term_setup(TERM_REC *term)
512 {
513         GString *str;
514 #ifdef HAVE_TERMINFO
515         int err;
516 #endif
517         char *term_env;
518
519         term_env = getenv("TERM");
520         if (term_env == NULL) {
521                 fprintf(term->out, "TERM environment not set\n");
522                 return 0;
523         }
524
525 #ifdef HAVE_TERMINFO
526         if (setupterm(term_env, 1, &err) != 0) {
527                 fprintf(term->out, "setupterm() failed for TERM=%s: %d\n", term_env, err);
528                 return 0;
529         }
530 #else
531         if (tgetent(term->buffer1, term_env) < 1)
532         {
533                 fprintf(term->out, "Termcap not found for TERM=%s\n", term_env);
534                 return -1;
535         }
536 #endif
537
538         term_fill_capabilities(term);
539
540         /* Cursor movement */
541         if (term->TI_cup)
542                 term->move = _move_cup;
543         else if (term->TI_hpa && term->TI_vpa)
544                 term->move = _move_pa;
545         else {
546                 fprintf(term->out, "Terminal doesn't support cursor movement\n");
547                 return 0;
548         }
549         term->move_relative = _move_relative;
550         term->set_cursor_visible = term->TI_civis && term->TI_cnorm ?
551                 _set_cursor_visible : _ignore_parm;
552
553         /* Scrolling */
554         if ((term->TI_csr || term->TI_wind) && term->TI_rin && term->TI_indn)
555                 term->scroll = _scroll_region;
556         else if (term->TI_il && term->TI_dl)
557                 term->scroll = _scroll_line;
558         else if ((term->TI_csr || term->TI_wind) && term->TI_ri && term->TI_ind)
559                 term->scroll = _scroll_region_1;
560         else if (term->scroll == NULL && (term->TI_il1 && term->TI_dl1))
561                 term->scroll = _scroll_line_1;
562         else if (term->scroll == NULL) {
563                 fprintf(term->out, "Terminal doesn't support scrolling\n");
564                 return 0;
565         }
566
567         /* Clearing screen */
568         if (term->TI_clear)
569                 term->clear = _clear_screen;
570         else if (term->TI_ed)
571                 term->clear = _clear_eos;
572         else if (term->TI_dl)
573                 term->clear = _clear_del;
574         else if (term->TI_dl1)
575                 term->clear = _clear_del_1;
576         else {
577                 /* we could do this by line inserts as well, but don't
578                    bother - if some terminal has insert line it most probably
579                    has delete line as well, if not a regular clear screen */
580                 fprintf(term->out, "Terminal doesn't support clearing screen\n");
581                 return 0;
582         }
583
584         /* Clearing to end of line */
585         if (term->TI_el)
586                 term->clrtoeol = _clrtoeol;
587         else {
588                 fprintf(term->out, "Terminal doesn't support clearing to end of line\n");
589                 return 0;
590         }
591
592         /* Repeating character */
593         if (term->TI_rep)
594                 term->repeat = _repeat;
595         else
596                 term->repeat = _repeat_manual;
597
598         /* Bold, underline, standout */
599         term->set_bold = term->TI_bold ? _set_bold : _ignore;
600         term->set_uline = term->TI_smul && term->TI_rmul ?
601                 _set_uline : _ignore_parm;
602         term->set_standout = term->TI_smso && term->TI_rmso ?
603                 _set_standout : _ignore_parm;
604
605         /* Create a string to set all attributes off */
606         str = g_string_new(NULL);
607         if (term->TI_sgr0)
608                 g_string_append(str, term->TI_sgr0);
609         if (term->TI_rmul && (term->TI_sgr0 == NULL || strcmp(term->TI_rmul, term->TI_sgr0) != 0))
610                 g_string_append(str, term->TI_rmul);
611         if (term->TI_rmso && (term->TI_sgr0 == NULL || strcmp(term->TI_rmso, term->TI_sgr0) != 0))
612                 g_string_append(str, term->TI_rmso);
613         term->TI_normal = str->str;
614         g_string_free(str, FALSE);
615         term->set_normal = _set_normal;
616
617         term->beep = term->TI_bel ? _beep : _ignore;
618
619         terminfo_setup_colors(term, FALSE);
620         terminfo_cont(term);
621         return 1;
622 }
623
624 TERM_REC *terminfo_core_init(FILE *in, FILE *out)
625 {
626         TERM_REC *old_term, *term;
627
628         old_term = current_term;
629         current_term = term = g_new0(TERM_REC, 1);
630
631         term->in = in;
632         term->out = out;
633
634         if (!term_setup(term)) {
635                 g_free(term);
636                 term = NULL;
637         }
638
639         current_term = old_term;
640         return term;
641 }
642
643 void terminfo_core_deinit(TERM_REC *term)
644 {
645         TERM_REC *old_term;
646
647         old_term = current_term;
648         current_term = term;
649         term->set_normal(term);
650         current_term = old_term;
651
652         terminfo_stop(term);
653
654         g_free(term->TI_normal);
655         terminfo_colors_deinit(term);
656
657         g_free(term);
658 }