summaryrefslogtreecommitdiffstats
path: root/src/fe-text/term-terminfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-text/term-terminfo.c')
-rw-r--r--src/fe-text/term-terminfo.c803
1 files changed, 803 insertions, 0 deletions
diff --git a/src/fe-text/term-terminfo.c b/src/fe-text/term-terminfo.c
new file mode 100644
index 0000000..9902bc0
--- /dev/null
+++ b/src/fe-text/term-terminfo.c
@@ -0,0 +1,803 @@
+/*
+ term-terminfo.c : irssi
+
+ Copyright (C) 2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/terminfo-core.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+#include <irssi/src/core/utf8.h>
+
+#include <signal.h>
+#include <termios.h>
+#include <stdio.h>
+
+#ifdef HAVE_TERM_H
+#include <term.h>
+#else
+/* TODO: This needs arguments, starting with C2X. */
+int tputs();
+#endif
+
+/* returns number of characters in the beginning of the buffer being a
+ a single character, or -1 if more input is needed. The character will be
+ saved in result */
+typedef int (*TERM_INPUT_FUNC)(const unsigned char *buffer, int size,
+ unichar *result);
+
+struct _TERM_WINDOW {
+ /* Terminal to use for window */
+ TERM_REC *term;
+
+ /* Area for window in terminal */
+ int x, y;
+ int width, height;
+};
+
+TERM_WINDOW *root_window;
+
+static char *term_lines_empty; /* 1 if line is entirely empty */
+static int vcmove, vcx, vcy, curs_visible;
+static int crealx, crealy, cforcemove;
+static int curs_x, curs_y;
+
+static unsigned int last_fg, last_bg;
+static int last_attrs;
+
+static GSource *sigcont_source;
+static volatile sig_atomic_t got_sigcont;
+static int freeze_counter;
+
+static TERM_INPUT_FUNC input_func;
+static unsigned char term_inbuf[256];
+static int term_inbuf_pos;
+
+/* SIGCONT handler */
+static void sig_cont(int p)
+{
+ got_sigcont = TRUE;
+}
+
+/* SIGCONT GSource */
+static gboolean sigcont_prepare(GSource *source, gint *timeout)
+{
+ *timeout = -1;
+ return got_sigcont;
+}
+
+static gboolean sigcont_check(GSource *source)
+{
+ return got_sigcont;
+}
+
+static gboolean sigcont_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
+{
+ got_sigcont = FALSE;
+ if (callback == NULL)
+ return TRUE;
+ return callback(user_data);
+}
+
+static gboolean do_redraw(gpointer unused)
+{
+ terminfo_cont(current_term);
+ irssi_redraw();
+
+ return 1;
+}
+
+static GSourceFuncs sigcont_funcs = {
+ .prepare = sigcont_prepare,
+ .check = sigcont_check,
+ .dispatch = sigcont_dispatch
+};
+
+static void term_atexit(void)
+{
+ if (!quitting && current_term && current_term->TI_rmcup) {
+ /* Unexpected exit, avoid switching out of alternate screen
+ to keep any on-screen errors (like noperl_die()'s) */
+ current_term->TI_rmcup = NULL;
+ }
+
+ term_deinit();
+}
+
+int term_init(void)
+{
+ struct sigaction act;
+ int width, height;
+
+ last_fg = last_bg = -1;
+ last_attrs = 0;
+ vcx = vcy = 0; crealx = crealy = -1;
+ vcmove = FALSE; cforcemove = TRUE;
+ curs_visible = TRUE;
+
+ current_term = terminfo_core_init(stdin, stdout);
+ if (current_term == NULL)
+ return FALSE;
+
+ if (term_get_size(&width, &height)) {
+ current_term->width = width;
+ current_term->height = height;
+ }
+
+ /* grab CONT signal */
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sig_cont;
+ sigaction(SIGCONT, &act, NULL);
+ sigcont_source = g_source_new(&sigcont_funcs, sizeof(GSource));
+ g_source_set_callback(sigcont_source, do_redraw, NULL, NULL);
+ g_source_attach(sigcont_source, NULL);
+
+ curs_x = curs_y = 0;
+ term_width = current_term->width;
+ term_height = current_term->height;
+ root_window = term_window_create(0, 0, term_width, term_height);
+
+ term_lines_empty = g_new0(char, term_height);
+
+ term_set_input_type(TERM_TYPE_8BIT);
+ term_common_init();
+ atexit(term_atexit);
+ return TRUE;
+}
+
+void term_deinit(void)
+{
+ if (current_term != NULL) {
+ signal(SIGCONT, SIG_DFL);
+ g_source_destroy(sigcont_source);
+ g_source_unref(sigcont_source);
+
+ term_common_deinit();
+ terminfo_core_deinit(current_term);
+ current_term = NULL;
+ }
+}
+
+static void term_move_real(void)
+{
+ if (vcx != crealx || vcy != crealy || cforcemove) {
+ if (curs_visible) {
+ terminfo_set_cursor_visible(FALSE);
+ curs_visible = FALSE;
+ }
+
+ if (cforcemove) {
+ crealx = crealy = -1;
+ cforcemove = FALSE;
+ }
+ terminfo_move_relative(crealx, crealy, vcx, vcy);
+ crealx = vcx; crealy = vcy;
+ }
+
+ vcmove = FALSE;
+}
+
+/* Cursor position is unknown - move it immediately to known position */
+static void term_move_reset(int x, int y)
+{
+ if (x >= term_width) x = term_width-1;
+ if (y >= term_height) y = term_height-1;
+
+ vcx = x; vcy = y;
+ cforcemove = TRUE;
+ term_move_real();
+}
+
+/* Resize terminal - if width or height is negative,
+ the new size is unknown and should be figured out somehow */
+void term_resize(int width, int height)
+{
+ if (width < 0 || height < 0) {
+ width = current_term->width;
+ height = current_term->height;
+ }
+
+ if (term_width != width || term_height != height) {
+ term_width = current_term->width = width;
+ term_height = current_term->height = height;
+ term_window_move(root_window, 0, 0, term_width, term_height);
+
+ g_free(term_lines_empty);
+ term_lines_empty = g_new0(char, term_height);
+ }
+
+ term_move_reset(0, 0);
+}
+
+void term_resize_final(int width, int height)
+{
+}
+
+/* Returns TRUE if terminal has colors */
+int term_has_colors(void)
+{
+ return current_term->TI_colors > 0;
+}
+
+/* Force the colors on any way you can */
+void term_force_colors(int set)
+{
+ terminfo_setup_colors(current_term, set);
+}
+
+/* Clear screen */
+void term_clear(void)
+{
+ term_set_color(root_window, ATTR_RESET);
+ terminfo_clear();
+ term_move_reset(0, 0);
+
+ memset(term_lines_empty, 1, term_height);
+}
+
+/* Beep */
+void term_beep(void)
+{
+ terminfo_beep(current_term);
+}
+
+/* Create a new window in terminal */
+TERM_WINDOW *term_window_create(int x, int y, int width, int height)
+{
+ TERM_WINDOW *window;
+
+ window = g_new0(TERM_WINDOW, 1);
+ window->term = current_term;
+ window->x = x; window->y = y;
+ window->width = width; window->height = height;
+ return window;
+}
+
+/* Destroy a terminal window */
+void term_window_destroy(TERM_WINDOW *window)
+{
+ g_free(window);
+}
+
+/* Move/resize a window */
+void term_window_move(TERM_WINDOW *window, int x, int y,
+ int width, int height)
+{
+ window->x = x;
+ window->y = y;
+ window->width = width;
+ window->height = height;
+}
+
+/* Clear window */
+void term_window_clear(TERM_WINDOW *window)
+{
+ int y;
+
+ terminfo_set_normal();
+ if (window->y == 0 && window->height == term_height && window->width == term_width) {
+ term_clear();
+ } else {
+ for (y = 0; y < window->height; y++) {
+ term_move(window, 0, y);
+ term_clrtoeol(window);
+ }
+ }
+}
+
+/* Scroll window up/down */
+void term_window_scroll(TERM_WINDOW *window, int count)
+{
+ int y;
+
+ terminfo_scroll(window->y, window->y+window->height-1, count);
+ term_move_reset(vcx, vcy);
+
+ /* set the newly scrolled area dirty */
+ for (y = 0; (window->y+y) < term_height && y < window->height; y++)
+ term_lines_empty[window->y+y] = FALSE;
+}
+
+inline static int term_putchar(int c)
+{
+ return fputc(c, current_term->out);
+}
+
+static int termctl_set_color_24bit(int bg, unsigned int lc)
+{
+ static char buf[20];
+ const unsigned char color[] = { lc >> 16, lc >> 8, lc };
+
+ if (!term_use_colors24) {
+ if (bg)
+ terminfo_set_bg(color_24bit_256(color));
+ else
+ terminfo_set_fg(color_24bit_256(color));
+ return -1;
+ }
+
+ /* \e[x8;2;...;...;...m */
+ sprintf(buf, "\033[%d8;2;%d;%d;%dm", bg ? 4 : 3, color[0], color[1], color[2]);
+ return tputs(buf, 0, term_putchar);
+}
+
+#define COLOR_RESET UINT_MAX
+#define COLOR_BLACK24 COLOR_RESET - 1
+
+/* Change active color */
+#ifdef TERM_TRUECOLOR
+void term_set_color2(TERM_WINDOW *window, int col, unsigned int fgcol24, unsigned int bgcol24)
+#else
+void term_set_color(TERM_WINDOW *window, int col)
+#endif
+{
+ int set_normal;
+
+ unsigned int fg, bg;
+#ifdef TERM_TRUECOLOR
+ if (col & ATTR_FGCOLOR24) {
+ if (fgcol24)
+ fg = fgcol24 << 8;
+ else
+ fg = COLOR_BLACK24;
+ } else
+#endif
+ fg = (col & FG_MASK);
+
+#ifdef TERM_TRUECOLOR
+ if (col & ATTR_BGCOLOR24) {
+ if (bgcol24)
+ bg = bgcol24 << 8;
+ else
+ bg = COLOR_BLACK24;
+ } else
+#endif
+ bg = ((col & BG_MASK) >> BG_SHIFT);
+
+ if (!term_use_colors && bg > 0)
+ col |= ATTR_REVERSE;
+
+ set_normal = ((col & ATTR_RESETFG) && last_fg != COLOR_RESET) ||
+ ((col & ATTR_RESETBG) && last_bg != COLOR_RESET);
+ if (((last_attrs & ATTR_BOLD) && (col & ATTR_BOLD) == 0) ||
+ ((last_attrs & ATTR_REVERSE) && (col & ATTR_REVERSE) == 0) ||
+ ((last_attrs & ATTR_BLINK) && (col & ATTR_BLINK) == 0)) {
+ /* we'll need to get rid of bold/blink/reverse - this
+ can only be done with setting the default color */
+ set_normal = TRUE;
+ }
+
+ if (set_normal) {
+ last_fg = last_bg = COLOR_RESET;
+ last_attrs = 0;
+ terminfo_set_normal();
+ }
+
+ /* set foreground color */
+ if (fg != last_fg &&
+ (fg != 0 || (col & ATTR_RESETFG) == 0)) {
+ if (term_use_colors) {
+ last_fg = fg;
+ if (fg >> 8)
+ termctl_set_color_24bit(0,
+ last_fg == COLOR_BLACK24 ? 0
+ : last_fg >> 8);
+ else
+ terminfo_set_fg(last_fg);
+ }
+ }
+
+ /* set background color */
+ if (window && window->term->TI_colors &&
+ (term_color256map[bg&0xff]&8) == window->term->TI_colors)
+ col |= ATTR_BLINK;
+ if (col & ATTR_BLINK)
+ current_term->set_blink(current_term);
+
+ if (bg != last_bg &&
+ (bg != 0 || (col & ATTR_RESETBG) == 0)) {
+ if (term_use_colors) {
+ last_bg = bg;
+ if (bg >> 8)
+ termctl_set_color_24bit(1,
+ last_bg == COLOR_BLACK24 ? 0
+ : last_bg >> 8);
+ else
+ terminfo_set_bg(last_bg);
+ }
+ }
+
+ /* reversed text */
+ if (col & ATTR_REVERSE)
+ terminfo_set_reverse();
+
+ /* bold */
+ if (window && window->term->TI_colors &&
+ (term_color256map[fg&0xff]&8) == window->term->TI_colors)
+ col |= ATTR_BOLD;
+ if (col & ATTR_BOLD)
+ terminfo_set_bold();
+
+ /* underline */
+ if (col & ATTR_UNDERLINE) {
+ if ((last_attrs & ATTR_UNDERLINE) == 0)
+ terminfo_set_uline(TRUE);
+ } else if (last_attrs & ATTR_UNDERLINE)
+ terminfo_set_uline(FALSE);
+
+ /* italic */
+ if (col & ATTR_ITALIC) {
+ if ((last_attrs & ATTR_ITALIC) == 0)
+ terminfo_set_italic(TRUE);
+ } else if (last_attrs & ATTR_ITALIC)
+ terminfo_set_italic(FALSE);
+
+ /* update the new attribute settings whilst ignoring color values. */
+ last_attrs = col & ~( BG_MASK | FG_MASK );
+}
+
+void term_move(TERM_WINDOW *window, int x, int y)
+{
+ if (x >= 0 && y >= 0) {
+ vcmove = TRUE;
+ vcx = x+window->x;
+ vcy = y+window->y;
+
+ if (vcx >= term_width)
+ vcx = term_width-1;
+ if (vcy >= term_height)
+ vcy = term_height-1;
+ }
+}
+
+static void term_printed_text(int count)
+{
+ term_lines_empty[vcy] = FALSE;
+
+ /* if we continued writing past the line, wrap to next line.
+ However, next term_move() really shouldn't try to cache
+ the move, otherwise terminals would try to combine the
+ last word in upper line with first word in lower line. */
+ vcx += count;
+ while (vcx >= term_width) {
+ vcx -= term_width;
+ if (vcy < term_height-1) vcy++;
+ if (vcx > 0) term_lines_empty[vcy] = FALSE;
+ }
+
+ crealx += count;
+ if (crealx >= term_width)
+ cforcemove = TRUE;
+}
+
+void term_addch(TERM_WINDOW *window, char chr)
+{
+ if (vcmove) term_move_real();
+
+ /* With UTF-8, move cursor only if this char is either
+ single-byte (8. bit off) or beginning of multibyte
+ (7. bit off) */
+ if (term_type != TERM_TYPE_UTF8 ||
+ (chr & 0x80) == 0 || (chr & 0x40) == 0) {
+ term_printed_text(1);
+ }
+
+ putc(chr, window->term->out);
+}
+
+static void term_addch_utf8(TERM_WINDOW *window, unichar chr)
+{
+ char buf[10];
+ int i, len;
+
+ len = g_unichar_to_utf8(chr, buf);
+ for (i = 0; i < len; i++)
+ putc(buf[i], window->term->out);
+}
+
+void term_add_unichar(TERM_WINDOW *window, unichar chr)
+{
+ if (vcmove) term_move_real();
+
+ switch (term_type) {
+ case TERM_TYPE_UTF8:
+ term_printed_text(unichar_isprint(chr) ? i_wcwidth(chr) : 1);
+ term_addch_utf8(window, chr);
+ break;
+ case TERM_TYPE_BIG5:
+ if (chr > 0xff) {
+ term_printed_text(2);
+ putc((chr >> 8) & 0xff, window->term->out);
+ } else {
+ term_printed_text(1);
+ }
+ putc((chr & 0xff), window->term->out);
+ break;
+ default:
+ term_printed_text(1);
+ putc(chr, window->term->out);
+ break;
+ }
+}
+
+int term_addstr(TERM_WINDOW *window, const char *str)
+{
+ int len, raw_len;
+ unichar tmp;
+ const char *ptr;
+
+ if (vcmove) term_move_real();
+
+ len = 0;
+ raw_len = strlen(str);
+
+ /* The string length depends on the terminal encoding */
+
+ ptr = str;
+
+ if (term_type == TERM_TYPE_UTF8) {
+ while (*ptr != '\0') {
+ tmp = g_utf8_get_char_validated(ptr, -1);
+ /* On utf8 error, treat as single byte and try to
+ continue interpreting rest of string as utf8 */
+ if (tmp == (gunichar)-1 || tmp == (gunichar)-2) {
+ len++;
+ ptr++;
+ } else {
+ len += unichar_isprint(tmp) ? i_wcwidth(tmp) : 1;
+ ptr = g_utf8_next_char(ptr);
+ }
+ }
+ } else
+ len = raw_len;
+
+ term_printed_text(len);
+
+ /* Use strlen() here since we need the number of raw bytes */
+ fwrite(str, 1, raw_len, window->term->out);
+
+ return len;
+}
+
+void term_clrtoeol(TERM_WINDOW *window)
+{
+ if (vcx < window->x) {
+ /* we just wrapped outside of the split, warp the cursor back into the window */
+ vcx += window->x;
+ vcmove = TRUE;
+ }
+ if (window->x + window->width < term_width) {
+ /* we need to fill a vertical split */
+ if (vcmove) term_move_real();
+ terminfo_repeat(' ', window->x + window->width - vcx + 1);
+ terminfo_move(vcx, vcy);
+ term_lines_empty[vcy] = FALSE;
+ } else {
+ /* clrtoeol() doesn't necessarily understand colors */
+ if (last_fg == -1 && last_bg == -1 &&
+ (last_attrs & (ATTR_UNDERLINE|ATTR_REVERSE|ATTR_ITALIC)) == 0) {
+ if (!term_lines_empty[vcy]) {
+ if (vcmove) term_move_real();
+ terminfo_clrtoeol();
+ if (vcx == 0) term_lines_empty[vcy] = TRUE;
+ }
+ } else if (vcx < term_width) {
+ /* we'll need to fill the line ourself. */
+ if (vcmove) term_move_real();
+ terminfo_repeat(' ', term_width-vcx);
+ terminfo_move(vcx, vcy);
+ term_lines_empty[vcy] = FALSE;
+ }
+ }
+}
+
+void term_window_clrtoeol(TERM_WINDOW* window, int ypos)
+{
+ if (ypos >= 0 && window->y + ypos != vcy) {
+ /* the line is already full */
+ return;
+ }
+ term_clrtoeol(window);
+ if (window->x + window->width < term_width) {
+ gui_printtext_window_border(window->x + window->width, window->y + ypos);
+ term_set_color(window, ATTR_RESET);
+ }
+}
+
+void term_window_clrtoeol_abs(TERM_WINDOW* window, int ypos)
+{
+ term_window_clrtoeol(window, ypos - window->y);
+}
+
+void term_move_cursor(int x, int y)
+{
+ curs_x = x;
+ curs_y = y;
+}
+
+void term_refresh(TERM_WINDOW *window)
+{
+ if (freeze_counter > 0)
+ return;
+
+ term_move(root_window, curs_x, curs_y);
+ term_move_real();
+
+ if (!curs_visible) {
+ terminfo_set_cursor_visible(TRUE);
+ curs_visible = TRUE;
+ }
+
+ term_set_color(window, ATTR_RESET);
+ fflush(window != NULL ? window->term->out : current_term->out);
+}
+
+void term_refresh_freeze(void)
+{
+ freeze_counter++;
+}
+
+void term_refresh_thaw(void)
+{
+ if (--freeze_counter == 0)
+ term_refresh(NULL);
+}
+
+void term_stop(void)
+{
+ terminfo_stop(current_term);
+ kill(getpid(), SIGTSTP);
+ /* this call needs to stay here in case the TSTP was ignored,
+ because then we never see a CONT to call the restoration
+ code. On the other hand we also cannot remove the CONT
+ handler because then nothing would restore the screen when
+ Irssi is killed with TSTP/STOP from external. */
+ terminfo_cont(current_term);
+ irssi_redraw();
+}
+
+static int input_utf8(const unsigned char *buffer, int size, unichar *result)
+{
+ unichar c = g_utf8_get_char_validated((char *) buffer, size);
+
+ /* GLib >= 2.63 do not accept Unicode NUL anymore */
+ if (c == (unichar) -2 && *buffer == 0 && size > 0)
+ c = 0;
+
+ switch (c) {
+ case (unichar)-1:
+ /* not UTF8 - fallback to 8bit ascii */
+ *result = *buffer;
+ return 1;
+ case (unichar)-2:
+ /* need more data */
+ return -1;
+ default:
+ *result = c;
+ return g_utf8_skip[*buffer];
+ }
+}
+
+static int input_big5(const unsigned char *buffer, int size, unichar *result)
+{
+ if (is_big5_hi(*buffer)) {
+ /* could be */
+ if (size == 1)
+ return -1;
+
+ if (is_big5_los(buffer[1]) || is_big5_lox(buffer[1])) {
+ *result = buffer[1] + ((int) *buffer << 8);
+ return 2;
+ }
+ }
+
+ *result = *buffer;
+ return 1;
+}
+
+static int input_8bit(const unsigned char *buffer, int size, unichar *result)
+{
+ *result = *buffer;
+ return 1;
+}
+
+void term_set_input_type(int type)
+{
+ switch (type) {
+ case TERM_TYPE_UTF8:
+ input_func = input_utf8;
+ break;
+ case TERM_TYPE_BIG5:
+ input_func = input_big5;
+ break;
+ default:
+ input_func = input_8bit;
+ }
+}
+
+void term_gets(GArray *buffer, int *line_count)
+{
+ int ret, i, char_len;
+
+ /* fread() doesn't work */
+
+ ret = read(fileno(current_term->in),
+ term_inbuf + term_inbuf_pos, sizeof(term_inbuf)-term_inbuf_pos);
+ if (ret == 0) {
+ /* EOF - terminal got lost */
+ ret = -1;
+ } else if (ret == -1 && (errno == EINTR || errno == EAGAIN))
+ ret = 0;
+ if (ret == -1)
+ signal_emit("command quit", 1, "Lost terminal");
+
+ if (ret > 0) {
+ /* convert input to unichars. */
+ term_inbuf_pos += ret;
+ for (i = 0; i < term_inbuf_pos; ) {
+ unichar key;
+ char_len = input_func(term_inbuf+i, term_inbuf_pos-i,
+ &key);
+ if (char_len < 0)
+ break;
+ g_array_append_val(buffer, key);
+ if (key == '\r' || key == '\n')
+ (*line_count)++;
+
+ i += char_len;
+ }
+
+ if (i >= term_inbuf_pos)
+ term_inbuf_pos = 0;
+ else if (i > 0) {
+ memmove(term_inbuf, term_inbuf+i, term_inbuf_pos-i);
+ term_inbuf_pos -= i;
+ }
+ }
+}
+
+static const char* term_env_warning =
+ "You seem to be running Irssi inside %2$s, but the TERM environment variable "
+ "is set to '%1$s', which can cause display glitches.\n"
+ "Consider changing TERM to '%2$s' or '%2$s-256color' instead.";
+
+void term_environment_check(void)
+{
+ const char *term, *sty, *tmux, *multiplexer;
+
+ term = g_getenv("TERM");
+ sty = g_getenv("STY");
+ tmux = g_getenv("TMUX");
+
+ multiplexer = (sty && *sty) ? "screen" :
+ (tmux && *tmux) ? "tmux" : NULL;
+
+ if (!multiplexer) {
+ return;
+ }
+
+ if (term && (g_str_has_prefix(term, "screen") ||
+ g_str_has_prefix(term, "tmux"))) {
+ return;
+ }
+
+ g_warning(term_env_warning, term, multiplexer);
+}