irssi/src/fe-text/term-terminfo.c
Ailin Nemui 836d386823 Add bounds check to array access in terminfo driver.
this fixes a crash due to illegal memory access that can occur if
something is printed to the screen on the "terminal resized"
handler. It is not clear to me whether this race condition can be
triggered by external incoming messages, but it might be better safe
than sorry.
2014-08-09 11:40:37 +02:00

690 lines
16 KiB
C

/*
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 "signals.h"
#include "term.h"
#include "terminfo-core.h"
#include "fe-windows.h"
#include "utf8.h"
#include <signal.h>
#include <termios.h>
#include <stdio.h>
/* 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
};
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_deinit);
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) {
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);
}
/* copied from terminfo-core.c */
int tputs();
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 && (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 && (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) ? mk_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;
}
}
void term_addstr(TERM_WINDOW *window, const char *str)
{
int len;
if (vcmove) term_move_real();
len = strlen(str); /* FIXME utf8 or big5 */
term_printed_text(len);
fwrite(str, 1, len, window->term->out);
}
void term_clrtoeol(TERM_WINDOW *window)
{
/* 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_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);
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);
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;
}
}
}