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