Added SILC Thread Queue API
[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         }
203
204         /* reset the scrolling region to full screen */
205         scroll_region_setup(term, 0, term->height-1);
206 }
207
208 /* Scroll (parm_insert_line+parm_delete_line / il+dl) */
209 static void _scroll_line(TERM_REC *term, int y1, int y2, int count)
210 {
211         /* setup the scrolling region to wanted area -
212            this might not necessarily work with il/dl, but at least it
213            looks better if it does */
214         scroll_region_setup(term, y1, y2);
215
216         if (count > 0) {
217                 term->move(term, 0, y1);
218                 tput(tparm(term->TI_dl, count, count));
219                 term->move(term, 0, y2-count+1);
220                 tput(tparm(term->TI_il, count, count));
221         } else if (count < 0) {
222                 term->move(term, 0, y2+count+1);
223                 tput(tparm(term->TI_dl, -count, -count));
224                 term->move(term, 0, y1);
225                 tput(tparm(term->TI_il, -count, -count));
226         }
227
228         /* reset the scrolling region to full screen */
229         scroll_region_setup(term, 0, term->height-1);
230 }
231
232 /* Scroll (insert_line+delete_line / il1+dl1) */
233 static void _scroll_line_1(TERM_REC *term, int y1, int y2, int count)
234 {
235         int i;
236
237         if (count > 0) {
238                 term->move(term, 0, y1);
239                 for (i = 0; i < count; i++)
240                         tput(tparm(term->TI_dl1));
241                 term->move(term, 0, y2-count+1);
242                 for (i = 0; i < count; i++)
243                         tput(tparm(term->TI_il1));
244         } else if (count < 0) {
245                 term->move(term, 0, y2+count+1);
246                 for (i = count; i < 0; i++)
247                         tput(tparm(term->TI_dl1));
248                 term->move(term, 0, y1);
249                 for (i = count; i < 0; i++)
250                         tput(tparm(term->TI_il1));
251         }
252 }
253
254 /* Clear screen (clear_screen / clear) */
255 static void _clear_screen(TERM_REC *term)
256 {
257         tput(tparm(term->TI_clear));
258 }
259
260 /* Clear screen (clr_eos / ed) */
261 static void _clear_eos(TERM_REC *term)
262 {
263         term->move(term, 0, 0);
264         tput(tparm(term->TI_ed));
265 }
266
267 /* Clear screen (parm_delete_line / dl) */
268 static void _clear_del(TERM_REC *term)
269 {
270         term->move(term, 0, 0);
271         tput(tparm(term->TI_dl, term->height, term->height));
272 }
273
274 /* Clear screen (delete_line / dl1) */
275 static void _clear_del_1(TERM_REC *term)
276 {
277         int i;
278
279         term->move(term, 0, 0);
280         for (i = 0; i < term->height; i++)
281                 tput(tparm(term->TI_dl1));
282 }
283
284 /* Clear to end of line (clr_eol / el) */
285 static void _clrtoeol(TERM_REC *term)
286 {
287         tput(tparm(term->TI_el));
288 }
289
290 /* Repeat character (rep / rp) */
291 static void _repeat(TERM_REC *term, int chr, int count)
292 {
293         tput(tparm(term->TI_rep, chr, count));
294 }
295
296 /* Repeat character (manual) */
297 static void _repeat_manual(TERM_REC *term, int chr, int count)
298 {
299         while (count > 0) {
300                 putc(chr, term->out);
301                 count--;
302         }
303 }
304
305 /* Reset all terminal attributes */
306 static void _set_normal(TERM_REC *term)
307 {
308         tput(tparm(term->TI_normal));
309 }
310
311 /* Bold on */
312 static void _set_bold(TERM_REC *term)
313 {
314         tput(tparm(term->TI_bold));
315 }
316
317 /* Underline on/off */
318 static void _set_uline(TERM_REC *term, int set)
319 {
320         tput(tparm(set ? term->TI_smul : term->TI_rmul));
321 }
322
323 /* Standout on/off */
324 static void _set_standout(TERM_REC *term, int set)
325 {
326         tput(tparm(set ? term->TI_smso : term->TI_rmso));
327 }
328
329 /* Change foreground color */
330 static void _set_fg(TERM_REC *term, int color)
331 {
332         tput(tparm(term->TI_fg[color & 0x0f]));
333 }
334
335 /* Change background color */
336 static void _set_bg(TERM_REC *term, int color)
337 {
338         tput(tparm(term->TI_bg[color & 0x0f]));
339 }
340
341 /* Beep */
342 static void _beep(TERM_REC *term)
343 {
344         tput(tparm(term->TI_bel));
345 }
346
347 static void _ignore(TERM_REC *term)
348 {
349 }
350
351 static void _ignore_parm(TERM_REC *term, int param)
352 {
353 }
354
355 static void term_fill_capabilities(TERM_REC *term)
356 {
357         int i, ival;
358         char *sval;
359         void *ptr;
360
361 #ifndef HAVE_TERMINFO
362         char *tptr = term->buffer2;
363 #endif
364         for (i = 0; i < sizeof(tcaps)/sizeof(tcaps[0]); i++) {
365                 ptr = (char *) term + (int) ((char *) tcaps[i].ptr - (char *) &temp_term);
366
367                 switch (tcaps[i].type) {
368                 case CAP_TYPE_FLAG:
369                         ival = term_getflag(tcaps[i]);
370                         *(int *)ptr = ival;
371                         break;
372                 case CAP_TYPE_INT:
373                         ival = term_getnum(tcaps[i]);
374                         *(int *)ptr = ival;
375                         break;
376                 case CAP_TYPE_STR:
377                         sval = term_getstr(tcaps[i], tptr);
378                         if (sval == (char *) -1)
379                                 *(char **)ptr = NULL;
380                         else
381                                 *(char **)ptr = sval;
382                         break;
383                 }
384         }
385 }
386
387 /* Terminal was resized - ask the width/height from terminfo again */
388 void terminfo_resize(TERM_REC *term)
389 {
390         /* FIXME: is this possible? */
391 }
392
393 static void terminfo_colors_deinit(TERM_REC *term)
394 {
395         int i;
396
397         if (terminfo_is_colors_set(term)) {
398                 for (i = 0; i < 16; i++) {
399                         g_free(term->TI_fg[i]);
400                         g_free(term->TI_bg[i]);
401                 }
402
403                 memset(term->TI_fg, 0, sizeof(term->TI_fg));
404                 memset(term->TI_bg, 0, sizeof(term->TI_fg));
405         }
406 }
407
408 /* Setup colors - if force is set, use ANSI-style colors if
409    terminal capabilities don't contain color codes */
410 void terminfo_setup_colors(TERM_REC *term, int force)
411 {
412         static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
413         const char *bold, *blink;
414         int i;
415
416         terminfo_colors_deinit(term);
417         term->has_colors = term->TI_setf || term->TI_setaf;
418
419         if (term->TI_setaf) {
420                 for (i = 0; i < 8; i++)
421                         term->TI_fg[i] = g_strdup(tparm(term->TI_setaf, ansitab[i], 0));
422         } else if (term->TI_setf) {
423                 for (i = 0; i < 8; i++)
424                         term->TI_fg[i] = g_strdup(tparm(term->TI_setf, i, 0));
425         } else if (force) {
426                 for (i = 0; i < 8; i++)
427                         term->TI_fg[i] = g_strdup_printf("\033[%dm", 30+ansitab[i]);
428         }
429
430         if (term->TI_setab) {
431                 for (i = 0; i < 8; i++)
432                         term->TI_bg[i] = g_strdup(tparm(term->TI_setab, ansitab[i], 0));
433         } else if (term->TI_setb) {
434                 for (i = 0; i < 8; i++)
435                         term->TI_bg[i] = g_strdup(tparm(term->TI_setb, i, 0));
436         } else if (force) {
437                 for (i = 0; i < 8; i++)
438                         term->TI_bg[i] = g_strdup_printf("\033[%dm", 40+ansitab[i]);
439         }
440
441         if (term->TI_setf || term->TI_setaf || force) {
442                 term->set_fg = _set_fg;
443                 term->set_bg = _set_bg;
444
445                 /* bold fg, blink bg */
446                 bold = term->TI_bold ? term->TI_bold : "";
447                 for (i = 0; i < 8; i++)
448                         term->TI_fg[i+8] = g_strconcat(bold, term->TI_fg[i], NULL);
449
450                 blink = term->TI_blink ? term->TI_blink : "";
451                 for (i = 0; i < 8; i++)
452                         term->TI_bg[i+8] = g_strconcat(blink, term->TI_bg[i], NULL);
453         } else {
454                 /* no colors */
455                 term->set_fg = term->set_bg = _ignore_parm;
456         }
457 }
458
459 static void terminfo_input_init(TERM_REC *term)
460 {
461         tcgetattr(fileno(term->in), &term->old_tio);
462         memcpy(&term->tio, &term->old_tio, sizeof(term->tio));
463
464         term->tio.c_lflag &= ~(ICANON | ECHO); /* CBREAK, no ECHO */
465         term->tio.c_cc[VMIN] = 1; /* read() is satisfied after 1 char */
466         term->tio.c_cc[VTIME] = 0; /* No timer */
467
468         /* Disable INTR, QUIT, VDSUSP and SUSP keys */
469         term->tio.c_cc[VINTR] = _POSIX_VDISABLE;
470         term->tio.c_cc[VQUIT] = _POSIX_VDISABLE;
471 #ifdef VDSUSP
472         term->tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
473 #endif
474 #ifdef VSUSP
475         term->tio.c_cc[VSUSP] = _POSIX_VDISABLE;
476 #endif
477
478         tcsetattr(fileno(term->in), TCSADRAIN, &term->tio);
479
480 }
481
482 static void terminfo_input_deinit(TERM_REC *term)
483 {
484         tcsetattr(fileno(term->in), TCSADRAIN, &term->old_tio);
485 }
486
487 void terminfo_cont(TERM_REC *term)
488 {
489         if (term->TI_smcup)
490                 tput(tparm(term->TI_smcup));
491         terminfo_input_init(term);
492 }
493
494 void terminfo_stop(TERM_REC *term)
495 {
496         /* reset colors */
497         terminfo_set_normal();
498         /* move cursor to bottom of the screen */
499         terminfo_move(0, term->height-1);
500
501         /* stop cup-mode */
502         if (term->TI_rmcup)
503                 tput(tparm(term->TI_rmcup));
504
505         /* reset input settings */
506         terminfo_input_deinit(term);
507         fflush(term->out);
508 }
509
510 static int term_setup(TERM_REC *term)
511 {
512         GString *str;
513 #ifdef HAVE_TERMINFO
514         int err;
515 #endif
516         char *term_env;
517
518         term_env = getenv("TERM");
519         if (term_env == NULL) {
520                 fprintf(term->out, "TERM environment not set\n");
521                 return 0;
522         }
523
524 #ifdef HAVE_TERMINFO
525         if (setupterm(term_env, 1, &err) != 0) {
526                 fprintf(term->out, "setupterm() failed for TERM=%s: %d\n", term_env, err);
527                 return 0;
528         }
529 #else
530         if (tgetent(term->buffer1, term_env) < 1)
531         {
532                 fprintf(term->out, "Termcap not found for TERM=%s\n", term_env);
533                 return 0;
534         }
535 #endif
536
537         term_fill_capabilities(term);
538
539         /* Cursor movement */
540         if (term->TI_cup)
541                 term->move = _move_cup;
542         else if (term->TI_hpa && term->TI_vpa)
543                 term->move = _move_pa;
544         else {
545                 fprintf(term->out, "Terminal doesn't support cursor movement\n");
546                 return 0;
547         }
548         term->move_relative = _move_relative;
549         term->set_cursor_visible = term->TI_civis && term->TI_cnorm ?
550                 _set_cursor_visible : _ignore_parm;
551
552         /* Scrolling */
553         if ((term->TI_csr || term->TI_wind) && term->TI_rin && term->TI_indn)
554                 term->scroll = _scroll_region;
555         else if (term->TI_il && term->TI_dl)
556                 term->scroll = _scroll_line;
557         else if ((term->TI_csr || term->TI_wind) && term->TI_ri && term->TI_ind)
558                 term->scroll = _scroll_region_1;
559         else if (term->scroll == NULL && (term->TI_il1 && term->TI_dl1))
560                 term->scroll = _scroll_line_1;
561         else if (term->scroll == NULL) {
562                 fprintf(term->out, "Terminal doesn't support scrolling\n");
563                 return 0;
564         }
565
566         /* Clearing screen */
567         if (term->TI_clear)
568                 term->clear = _clear_screen;
569         else if (term->TI_ed)
570                 term->clear = _clear_eos;
571         else if (term->TI_dl)
572                 term->clear = _clear_del;
573         else if (term->TI_dl1)
574                 term->clear = _clear_del_1;
575         else {
576                 /* we could do this by line inserts as well, but don't
577                    bother - if some terminal has insert line it most probably
578                    has delete line as well, if not a regular clear screen */
579                 fprintf(term->out, "Terminal doesn't support clearing screen\n");
580                 return 0;
581         }
582
583         /* Clearing to end of line */
584         if (term->TI_el)
585                 term->clrtoeol = _clrtoeol;
586         else {
587                 fprintf(term->out, "Terminal doesn't support clearing to end of line\n");
588                 return 0;
589         }
590
591         /* Repeating character */
592         if (term->TI_rep)
593                 term->repeat = _repeat;
594         else
595                 term->repeat = _repeat_manual;
596
597         /* Bold, underline, standout */
598         term->set_bold = term->TI_bold ? _set_bold : _ignore;
599         term->set_uline = term->TI_smul && term->TI_rmul ?
600                 _set_uline : _ignore_parm;
601         term->set_standout = term->TI_smso && term->TI_rmso ?
602                 _set_standout : _ignore_parm;
603
604         /* Create a string to set all attributes off */
605         str = g_string_new(NULL);
606         if (term->TI_sgr0)
607                 g_string_append(str, term->TI_sgr0);
608         if (term->TI_rmul && (term->TI_sgr0 == NULL || strcmp(term->TI_rmul, term->TI_sgr0) != 0))
609                 g_string_append(str, term->TI_rmul);
610         if (term->TI_rmso && (term->TI_sgr0 == NULL || strcmp(term->TI_rmso, term->TI_sgr0) != 0))
611                 g_string_append(str, term->TI_rmso);
612         term->TI_normal = str->str;
613         g_string_free(str, FALSE);
614         term->set_normal = _set_normal;
615
616         term->beep = term->TI_bel ? _beep : _ignore;
617
618         terminfo_setup_colors(term, FALSE);
619         terminfo_cont(term);
620         return 1;
621 }
622
623 TERM_REC *terminfo_core_init(FILE *in, FILE *out)
624 {
625         TERM_REC *old_term, *term;
626
627         old_term = current_term;
628         current_term = term = g_new0(TERM_REC, 1);
629
630         term->in = in;
631         term->out = out;
632
633         if (!term_setup(term)) {
634                 g_free(term);
635                 term = NULL;
636         }
637
638         current_term = old_term;
639         return term;
640 }
641
642 void terminfo_core_deinit(TERM_REC *term)
643 {
644         TERM_REC *old_term;
645
646         old_term = current_term;
647         current_term = term;
648         term->set_normal(term);
649         current_term = old_term;
650
651         terminfo_stop(term);
652
653         g_free(term->TI_normal);
654         terminfo_colors_deinit(term);
655
656         g_free(term);
657 }