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