Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / fe-text / term-terminfo.c
1 /*
2  term-terminfo.c : irssi
3
4     Copyright (C) 2001 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "term.h"
24 #include "terminfo-core.h"
25 #include "utf8.h"
26
27 #include <signal.h>
28
29 /* returns number of characters in the beginning of the buffer being a
30    a single character, or -1 if more input is needed. The character will be
31    saved in result */
32 typedef int (*TERM_INPUT_FUNC)(const unsigned char *buffer, int size,
33                                unichar *result);
34
35 struct _TERM_WINDOW {
36         /* Terminal to use for window */
37         TERM_REC *term;
38
39         /* Area for window in terminal */
40         int x, y;
41         int width, height;
42 };
43
44 TERM_WINDOW *root_window;
45
46 static char *term_lines_empty; /* 1 if line is entirely empty */
47 static int vcmove, vcx, vcy, curs_visible;
48 static int crealx, crealy, cforcemove;
49 static int curs_x, curs_y;
50 static int auto_detach;
51
52 static int last_fg, last_bg, last_attrs;
53
54 static int redraw_needed, redraw_tag;
55 static int freeze_counter;
56
57 static TERM_INPUT_FUNC input_func;
58 static unsigned char term_inbuf[256];
59 static int term_inbuf_pos;
60
61 /* SIGCONT handler */
62 static void sig_cont(int p)
63 {
64         redraw_needed = TRUE;
65         terminfo_cont(current_term);
66 }
67
68 static int redraw_timeout(void)
69 {
70         if (redraw_needed) {
71                 irssi_redraw();
72                 redraw_needed = FALSE;
73         }
74
75         return 1;
76 }
77
78 int term_init(void)
79 {
80         struct sigaction act;
81         int width, height;
82
83         last_fg = last_bg = -1;
84         last_attrs = 0;
85         vcx = vcy = 0; crealx = crealy = -1;
86         vcmove = FALSE; cforcemove = TRUE;
87         curs_visible = TRUE;
88
89         current_term = terminfo_core_init(stdin, stdout);
90         if (current_term == NULL)
91                 return FALSE;
92
93         if (term_get_size(&width, &height)) {
94                 current_term->width = width;
95                 current_term->height = height;
96         }
97
98         /* grab CONT signal */
99         sigemptyset(&act.sa_mask);
100         act.sa_flags = 0;
101         act.sa_handler = sig_cont;
102         sigaction(SIGCONT, &act, NULL);
103         redraw_tag = g_timeout_add(500, (GSourceFunc) redraw_timeout, NULL);
104
105         curs_x = curs_y = 0;
106         term_width = current_term->width;
107         term_height = current_term->height;
108         root_window = term_window_create(0, 0, term_width, term_height);
109         term_detached = FALSE;
110
111         term_lines_empty = g_new0(char, term_height);
112
113         term_set_input_type(TERM_TYPE_8BIT);
114         term_common_init();
115         g_atexit(term_deinit);
116         return TRUE;
117 }
118
119 void term_deinit(void)
120 {
121         if (current_term != NULL) {
122                 signal(SIGCONT, SIG_DFL);
123                 g_source_remove(redraw_tag);
124
125                 term_common_deinit();
126                 terminfo_core_deinit(current_term);
127                 current_term = NULL;
128         }
129 }
130
131 static void term_move_real(void)
132 {
133         if (term_detached) return;
134
135         if (vcx != crealx || vcy != crealy || cforcemove) {
136                 if (curs_visible) {
137                         terminfo_set_cursor_visible(FALSE);
138                         curs_visible = FALSE;
139                 }
140
141                 if (cforcemove) {
142                         crealx = crealy = -1;
143                         cforcemove = FALSE;
144                 }
145                 terminfo_move_relative(crealx, crealy, vcx, vcy);
146                 crealx = vcx; crealy = vcy;
147         }
148
149         vcmove = FALSE;
150 }
151
152 /* Cursor position is unknown - move it immediately to known position */
153 static void term_move_reset(int x, int y)
154 {
155         if (x >= term_width) x = term_width-1;
156         if (y >= term_height) y = term_height-1;
157
158         vcx = x; vcy = y;
159         cforcemove = TRUE;
160         term_move_real();
161 }
162
163 /* Resize terminal - if width or height is negative,
164    the new size is unknown and should be figured out somehow */
165 void term_resize(int width, int height)
166 {
167         if (width < 0 || height < 0) {
168                 terminfo_resize(current_term);
169                 width = current_term->width;
170                 height = current_term->height;
171         }
172
173         if (term_width != width || term_height != height) {
174                 term_width = current_term->width = width;
175                 term_height = current_term->height = height;
176                 term_window_move(root_window, 0, 0, term_width, term_height);
177
178                 g_free(term_lines_empty);
179                 term_lines_empty = g_new0(char, term_height);
180         }
181
182         term_move_reset(0, 0);
183 }
184
185 void term_resize_final(int width, int height)
186 {
187 }
188
189 /* Returns TRUE if terminal has colors */
190 int term_has_colors(void)
191 {
192         return current_term->has_colors;
193 }
194
195 /* Force the colors on any way you can */
196 void term_force_colors(int set)
197 {
198         if (term_detached) return;
199
200         terminfo_setup_colors(current_term, set);
201 }
202
203 /* Clear screen */
204 void term_clear(void)
205 {
206         if (term_detached) return;
207
208         term_set_color(root_window, ATTR_RESET);
209         terminfo_clear();
210         term_move_reset(0, 0);
211
212         memset(term_lines_empty, 1, term_height);
213 }
214
215 /* Beep */
216 void term_beep(void)
217 {
218         if (term_detached) return;
219
220         terminfo_beep(current_term);
221 }
222
223 /* Create a new window in terminal */
224 TERM_WINDOW *term_window_create(int x, int y, int width, int height)
225 {
226         TERM_WINDOW *window;
227
228         window = g_new0(TERM_WINDOW, 1);
229         window->term = current_term;
230         window->x = x; window->y = y;
231         window->width = width; window->height = height;
232         return window;
233 }
234
235 /* Destroy a terminal window */
236 void term_window_destroy(TERM_WINDOW *window)
237 {
238         g_free(window);
239 }
240
241 /* Move/resize a window */
242 void term_window_move(TERM_WINDOW *window, int x, int y,
243                       int width, int height)
244 {
245         window->x = x;
246         window->y = y;
247         window->width = width;
248         window->height = height;
249 }
250
251 /* Clear window */
252 void term_window_clear(TERM_WINDOW *window)
253 {
254         int y;
255
256         if (term_detached) return;
257
258         terminfo_set_normal();
259         if (window->y == 0 && window->height == term_height) {
260                 term_clear();
261         } else {
262                 for (y = 0; y < window->height; y++) {
263                         term_move(window, 0, y);
264                         term_clrtoeol(window);
265                 }
266         }
267 }
268
269 /* Scroll window up/down */
270 void term_window_scroll(TERM_WINDOW *window, int count)
271 {
272         int y;
273
274         if (term_detached) return;
275
276         terminfo_scroll(window->y, window->y+window->height-1, count);
277         term_move_reset(vcx, vcy);
278
279         /* set the newly scrolled area dirty */
280         for (y = 0; y < window->height; y++)
281                 term_lines_empty[window->y+y] = FALSE;
282 }
283
284 /* Change active color */
285 void term_set_color(TERM_WINDOW *window, int col)
286 {
287         int set_normal;
288
289         if (term_detached) return;
290
291         set_normal = ((col & ATTR_RESETFG) && last_fg != -1) ||
292                 ((col & ATTR_RESETBG) && last_bg != -1);
293         if (((last_attrs & ATTR_BOLD) && (col & ATTR_BOLD) == 0) ||
294             ((last_attrs & ATTR_BLINK) && (col & ATTR_BLINK) == 0)) {
295                 /* we'll need to get rid of bold/blink - this can only be
296                    done with setting the default color */
297                 set_normal = TRUE;
298         }
299
300         if (set_normal) {
301                 last_fg = last_bg = -1;
302                 last_attrs = 0;
303                 terminfo_set_normal();
304         }
305
306         if (!term_use_colors && (col & 0xf0) != 0)
307                 col |= ATTR_REVERSE;
308
309         /* reversed text (use standout) */
310         if (col & ATTR_REVERSE) {
311                 if ((last_attrs & ATTR_REVERSE) == 0)
312                         terminfo_set_standout(TRUE);
313         } else if (last_attrs & ATTR_REVERSE)
314                 terminfo_set_standout(FALSE);
315
316         /* set foreground color */
317         if ((col & 0x0f) != last_fg &&
318             ((col & 0x0f) != 0 || (col & ATTR_RESETFG) == 0)) {
319                 if (term_use_colors) {
320                         last_fg = col & 0x0f;
321                         terminfo_set_fg(last_fg);
322                 }
323         }
324
325         /* set background color */
326         if (col & ATTR_BLINK)
327                 col |= 0x80;
328         else if (col & 0x80)
329                 col |= ATTR_BLINK;
330
331         if ((col & 0xf0) >> 4 != last_bg &&
332             ((col & 0xf0) != 0 || (col & ATTR_RESETBG) == 0)) {
333                 if (term_use_colors) {
334                         last_bg = (col & 0xf0) >> 4;
335                         terminfo_set_bg(last_bg);
336                 }
337         }
338
339         /* bold */
340         if (col & 0x08)
341                 col |= ATTR_BOLD;
342         if (col & ATTR_BOLD)
343                 terminfo_set_bold();
344
345         /* underline */
346         if (col & ATTR_UNDERLINE) {
347                 if ((last_attrs & ATTR_UNDERLINE) == 0)
348                         terminfo_set_uline(TRUE);
349         } else if (last_attrs & ATTR_UNDERLINE)
350                 terminfo_set_uline(FALSE);
351
352         last_attrs = col & ~0xff;
353 }
354
355 void term_move(TERM_WINDOW *window, int x, int y)
356 {
357         if (x >= 0 && y >= 0) {
358         vcmove = TRUE;
359         vcx = x+window->x;
360         vcy = y+window->y;
361
362         if (vcx >= term_width)
363                 vcx = term_width-1;
364         if (vcy >= term_height)
365                 vcy = term_height-1;
366         }
367 }
368
369 static void term_printed_text(int count)
370 {
371         term_lines_empty[vcy] = FALSE;
372
373         /* if we continued writing past the line, wrap to next line.
374            However, next term_move() really shouldn't try to cache
375            the move, otherwise terminals would try to combine the
376            last word in upper line with first word in lower line. */
377         vcx += count;
378         while (vcx >= term_width) {
379                 vcx -= term_width;
380                 if (vcy < term_height-1) vcy++;
381                 if (vcx > 0) term_lines_empty[vcy] = FALSE;
382         }
383
384         crealx += count;
385         if (crealx >= term_width)
386                 cforcemove = TRUE;
387 }
388
389 void term_addch(TERM_WINDOW *window, int chr)
390 {
391         if (term_detached) return;
392
393         if (vcmove) term_move_real();
394
395         if (vcy < term_height-1 || vcx < term_width-1) {
396                 /* With UTF-8, move cursor only if this char is either
397                    single-byte (8. bit off) or beginning of multibyte
398                    (7. bit off) */
399                 if (term_type != TERM_TYPE_UTF8 ||
400                     (chr & 0x80) == 0 || (chr & 0x40) == 0) {
401                         term_printed_text(1);
402                 }
403
404                 putc(chr, window->term->out);
405         }
406 }
407
408 static void term_addch_utf8(TERM_WINDOW *window, unichar chr)
409 {
410         char buf[10];
411         int i, len;
412
413         len = utf16_char_to_utf8(chr, buf);
414         for (i = 0;  i < len; i++)
415                 putc(buf[i], window->term->out);
416 }
417
418 void term_add_unichar(TERM_WINDOW *window, unichar chr)
419 {
420         if (term_detached) return;
421
422         if (vcmove) term_move_real();
423         if (vcy == term_height-1 && vcx == term_width-1)
424                 return; /* last char in screen */
425
426         switch (term_type) {
427         case TERM_TYPE_UTF8:
428                 term_printed_text(utf8_width(chr));
429                 term_addch_utf8(window, chr);
430                 break;
431         case TERM_TYPE_BIG5:
432                 if (chr > 0xff) {
433                         term_printed_text(2);
434                         putc((chr >> 8) & 0xff, window->term->out);
435                 } else {
436                         term_printed_text(1);
437                 }
438                 putc((chr & 0xff), window->term->out);
439                 break;
440         default:
441                 term_printed_text(1);
442                 putc(chr, window->term->out);
443                 break;
444         }
445 }
446
447 void term_addstr(TERM_WINDOW *window, const char *str)
448 {
449         int len;
450
451         if (term_detached) return;
452
453         if (vcmove) term_move_real();
454         len = strlen(str); /* FIXME utf8 or big5 */
455         term_printed_text(len);
456
457         if (vcy != term_height || vcx != 0)
458                 fputs(str, window->term->out);
459         else
460                 fwrite(str, 1, len-1, window->term->out);
461 }
462
463 void term_clrtoeol(TERM_WINDOW *window)
464 {
465         if (term_detached) return;
466
467         /* clrtoeol() doesn't necessarily understand colors */
468         if (last_fg == -1 && last_bg == -1 &&
469             (last_attrs & (ATTR_UNDERLINE|ATTR_REVERSE)) == 0) {
470                 if (!term_lines_empty[vcy]) {
471                         if (vcmove) term_move_real();
472                         terminfo_clrtoeol();
473                         if (vcx == 0) term_lines_empty[vcy] = TRUE;
474                 }
475         } else if (vcx < term_width) {
476                 /* we'll need to fill the line ourself. */
477                 if (vcmove) term_move_real();
478                 terminfo_repeat(' ', term_width-vcx);
479                 terminfo_move(vcx, vcy);
480                 term_lines_empty[vcy] = FALSE;
481         }
482 }
483
484 void term_move_cursor(int x, int y)
485 {
486         curs_x = x;
487         curs_y = y;
488 }
489
490 void term_refresh(TERM_WINDOW *window)
491 {
492         if (term_detached || freeze_counter > 0)
493                 return;
494
495         term_move(root_window, curs_x, curs_y);
496         term_move_real();
497
498         if (!curs_visible) {
499                 terminfo_set_cursor_visible(TRUE);
500                 curs_visible = TRUE;
501         }
502
503         term_set_color(window, ATTR_RESET);
504         fflush(window != NULL ? window->term->out : current_term->out);
505 }
506
507 void term_refresh_freeze(void)
508 {
509         freeze_counter++;
510 }
511
512 void term_refresh_thaw(void)
513 {
514         if (--freeze_counter == 0)
515                 term_refresh(NULL);
516 }
517
518 void term_auto_detach(int set)
519 {
520         auto_detach = set;
521 }
522
523 void term_detach(void)
524 {
525         terminfo_stop(current_term);
526
527         fclose(current_term->in);
528         fclose(current_term->out);
529
530         current_term->in = NULL;
531         current_term->out = NULL;
532         term_detached = TRUE;
533 }
534
535 void term_attach(FILE *in, FILE *out)
536 {
537         current_term->in = in;
538         current_term->out = out;
539         term_detached = FALSE;
540
541         terminfo_cont(current_term);
542         irssi_redraw();
543 }
544
545 void term_stop(void)
546 {
547         if (term_detached) {
548                 kill(getpid(), SIGTSTP);
549         } else {
550                 terminfo_stop(current_term);
551                 kill(getpid(), SIGTSTP);
552                 terminfo_cont(current_term);
553                 irssi_redraw();
554         }
555 }
556
557 static int input_utf8(const unsigned char *buffer, int size, unichar *result)
558 {
559         const unsigned char *end = buffer;
560
561         switch (get_utf8_char(&end, size, result)) {
562         case -2:
563                 /* not UTF8 - fallback to 8bit ascii */
564                 *result = *buffer;
565                 return 1;
566         case -1:
567                 /* need more data */
568                 return -1;
569         default:
570                 return (int) (end-buffer)+1;
571         }
572 }
573
574 static int input_big5(const unsigned char *buffer, int size, unichar *result)
575 {
576         if (is_big5_hi(*buffer)) {
577                 /* could be */
578                 if (size == 1)
579                         return -1;
580
581                 if (is_big5_los(buffer[1]) || is_big5_lox(buffer[1])) {
582                         *result = buffer[1] + ((int) *buffer << 8);
583                         return 2;
584                 }
585         }
586
587         *result = *buffer;
588         return 1;
589 }
590
591 static int input_8bit(const unsigned char *buffer, int size, unichar *result)
592 {
593         *result = *buffer;
594         return 1;
595 }
596
597 void term_set_input_type(int type)
598 {
599         switch (type) {
600         case TERM_TYPE_UTF8:
601                 input_func = input_utf8;
602                 break;
603         case TERM_TYPE_BIG5:
604                 input_func = input_big5;
605                 break;
606         default:
607                 input_func = input_8bit;
608         }
609 }
610
611 int term_gets(unichar *buffer, int size)
612 {
613         int ret, i, char_len;
614
615         if (term_detached)
616                 return 0;
617
618         /* fread() doesn't work */
619         if (size > sizeof(term_inbuf)-term_inbuf_pos)
620                 size = sizeof(term_inbuf)-term_inbuf_pos;
621
622         ret = read(fileno(current_term->in),
623                    term_inbuf + term_inbuf_pos, size);
624         if (ret == 0) {
625                 /* EOF - terminal got lost */
626                 if (auto_detach)
627                         term_detach();
628                 ret = -1;
629         } else if (ret == -1 && (errno == EINTR || errno == EAGAIN))
630                 ret = 0;
631
632         if (ret > 0) {
633                 /* convert input to unichars. */
634                 term_inbuf_pos += ret;
635                 ret = 0;
636                 for (i = 0; i < term_inbuf_pos; ) {
637                         char_len = input_func(term_inbuf+i, term_inbuf_pos-i,
638                                               buffer);
639                         if (char_len < 0)
640                                 break;
641
642                         i += char_len;
643                         buffer++;
644                         ret++;
645                 }
646
647                 if (i >= term_inbuf_pos)
648                         term_inbuf_pos = 0;
649                 else if (i > 0) {
650                         memmove(term_inbuf, term_inbuf+i, term_inbuf_pos-i);
651                         term_inbuf_pos -= i;
652                 }
653         }
654
655         return ret;
656 }