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