irssi/src/fe-text/terminfo-core.c
Ailin Nemui 96a292d40e Finish 256 colour support for Irssi
256 colour patch is cleaned up and the remaining cases are made work,
this includes especially Theme support, which was not implemented
before. Changes not related to colours were reverted again, making a
review of the two patches against master easier to follow.

As a byproduct of the Hex-colour code parser, the 24bit colours are
also implemented. Actually using them in the terminal is guarded by a
compile time switch (as well as a run time switch), as it breaks the
existing colour protocol and requires additional storage.

To make a seamless usage, down-conversion is provided for 8 and 16
colours.

Diverging from Tom's approach, the colour protocol is reverted back to
the original one. Unfortunately, the changes required in the Theme
engine will break the API.

For more details, please refer to the patch documentation at either
http://irssi-docs.wikispaces.com/Notes-256-Colour or
https://github.com/shabble/irssi-docs/wiki/Notes-256-Colour
2014-06-30 02:41:34 +02:00

674 lines
18 KiB
C

#include "module.h"
#include "signals.h"
#include "terminfo-core.h"
#ifndef _POSIX_VDISABLE
# define _POSIX_VDISABLE 0
#endif
#define tput(s) tputs(s, 0, term_putchar)
inline static int term_putchar(int c)
{
return fputc(c, current_term->out);
}
/* Don't bother including curses.h because of these -
they might not even be defined there */
char *tparm();
int tputs();
#ifdef HAVE_TERMINFO
int setupterm();
char *tigetstr();
int tigetnum();
int tigetflag();
#define term_getstr(x, buffer) tigetstr(x.ti_name)
#define term_getnum(x) tigetnum(x.ti_name);
#define term_getflag(x) tigetflag(x.ti_name);
#else
int tgetent();
char *tgetstr();
int tgetnum();
int tgetflag();
#define term_getstr(x, buffer) tgetstr(x.tc_name, &buffer)
#define term_getnum(x) tgetnum(x.tc_name)
#define term_getflag(x) tgetflag(x.tc_name)
#endif
#define CAP_TYPE_FLAG 0
#define CAP_TYPE_INT 1
#define CAP_TYPE_STR 2
typedef struct {
const char *ti_name; /* terminfo name */
const char *tc_name; /* termcap name */
int type;
unsigned int offset;
} TERMINFO_REC;
TERM_REC *current_term;
/* Define only what we might need */
static TERMINFO_REC tcaps[] = {
/* Terminal size */
{ "cols", "co", CAP_TYPE_INT, G_STRUCT_OFFSET(TERM_REC, width) },
{ "lines", "li", CAP_TYPE_INT, G_STRUCT_OFFSET(TERM_REC, height) },
/* Cursor movement */
{ "smcup", "ti", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smcup) },
{ "rmcup", "te", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmcup) },
{ "cup", "cm", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cup) },
{ "hpa", "ch", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_hpa) },
{ "vpa", "vh", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_vpa) },
{ "cub1", "le", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cub1) },
{ "cuf1", "nd", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cuf1) },
{ "civis", "vi", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_civis) },
{ "cnorm", "ve", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cnorm) },
/* Scrolling */
{ "csr", "cs", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_csr) },
{ "wind", "wi", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_wind) },
{ "ri", "sr", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ri) },
{ "rin", "SR", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rin) },
{ "ind", "sf", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ind) },
{ "indn", "SF", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_indn) },
{ "il", "AL", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_il) },
{ "il1", "al", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_il1) },
{ "dl", "DL", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_dl) },
{ "dl1", "dl", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_dl1) },
/* Clearing screen */
{ "clear", "cl", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_clear) },
{ "ed", "cd", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ed) },
/* Clearing to end of line */
{ "el", "ce", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_el) },
/* Repeating character */
{ "rep", "rp", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rep) },
/* Colors */
{ "colors", "Co", CAP_TYPE_INT, G_STRUCT_OFFSET(TERM_REC, TI_colors) },
{ "sgr0", "me", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_sgr0) },
{ "smul", "us", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smul) },
{ "rmul", "ue", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmul) },
{ "smso", "so", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smso) },
{ "rmso", "se", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmso) },
{ "bold", "md", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_bold) },
{ "blink", "mb", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_blink) },
{ "setaf", "AF", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setaf) },
{ "setab", "AB", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setab) },
{ "setf", "Sf", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setf) },
{ "setb", "Sb", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setb) },
/* Beep */
{ "bel", "bl", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_bel) },
};
/* Move cursor (cursor_address / cup) */
static void _move_cup(TERM_REC *term, int x, int y)
{
tput(tparm(term->TI_cup, y, x));
}
/* Move cursor (column_address+row_address / hpa+vpa) */
static void _move_pa(TERM_REC *term, int x, int y)
{
tput(tparm(term->TI_hpa, x));
tput(tparm(term->TI_vpa, y));
}
/* Move cursor from a known position */
static void _move_relative(TERM_REC *term, int oldx, int oldy, int x, int y)
{
if (oldx == 0 && x == 0 && y == oldy+1) {
/* move to beginning of next line -
hope this works everywhere */
tput("\r\n");
return;
}
if (oldx > 0 && y == oldy) {
/* move cursor left/right */
if (x == oldx-1 && term->TI_cub1) {
tput(tparm(term->TI_cub1));
return;
}
if (x == oldx+1 && y == oldy && term->TI_cuf1) {
tput(tparm(term->TI_cuf1));
return;
}
}
/* fallback to absolute positioning */
if (term->TI_cup) {
tput(tparm(term->TI_cup, y, x));
return;
}
if (oldy != y)
tput(tparm(term->TI_vpa, y));
if (oldx != x)
tput(tparm(term->TI_hpa, x));
}
/* Set cursor visible/invisible */
static void _set_cursor_visible(TERM_REC *term, int set)
{
tput(tparm(set ? term->TI_cnorm : term->TI_civis));
}
#define scroll_region_setup(term, y1, y2) \
if ((term)->TI_csr != NULL) \
tput(tparm((term)->TI_csr, y1, y2)); \
else if ((term)->TI_wind != NULL) \
tput(tparm((term)->TI_wind, y1, y2, 0, (term)->width-1));
/* Scroll (change_scroll_region+parm_rindex+parm_index / csr+rin+indn) */
static void _scroll_region(TERM_REC *term, int y1, int y2, int count)
{
/* setup the scrolling region to wanted area */
scroll_region_setup(term, y1, y2);
term->move(term, 0, y1);
if (count > 0) {
term->move(term, 0, y2);
tput(tparm(term->TI_indn, count, count));
} else if (count < 0) {
term->move(term, 0, y1);
tput(tparm(term->TI_rin, -count, -count));
}
/* reset the scrolling region to full screen */
scroll_region_setup(term, 0, term->height-1);
}
/* Scroll (change_scroll_region+scroll_reverse+scroll_forward / csr+ri+ind) */
static void _scroll_region_1(TERM_REC *term, int y1, int y2, int count)
{
int i;
/* setup the scrolling region to wanted area */
scroll_region_setup(term, y1, y2);
if (count > 0) {
term->move(term, 0, y2);
for (i = 0; i < count; i++)
tput(tparm(term->TI_ind));
} else if (count < 0) {
term->move(term, 0, y1);
for (i = count; i < 0; i++)
tput(tparm(term->TI_ri));
}
/* reset the scrolling region to full screen */
scroll_region_setup(term, 0, term->height-1);
}
/* Scroll (parm_insert_line+parm_delete_line / il+dl) */
static void _scroll_line(TERM_REC *term, int y1, int y2, int count)
{
/* setup the scrolling region to wanted area -
this might not necessarily work with il/dl, but at least it
looks better if it does */
scroll_region_setup(term, y1, y2);
if (count > 0) {
term->move(term, 0, y1);
tput(tparm(term->TI_dl, count, count));
term->move(term, 0, y2-count+1);
tput(tparm(term->TI_il, count, count));
} else if (count < 0) {
term->move(term, 0, y2+count+1);
tput(tparm(term->TI_dl, -count, -count));
term->move(term, 0, y1);
tput(tparm(term->TI_il, -count, -count));
}
/* reset the scrolling region to full screen */
scroll_region_setup(term, 0, term->height-1);
}
/* Scroll (insert_line+delete_line / il1+dl1) */
static void _scroll_line_1(TERM_REC *term, int y1, int y2, int count)
{
int i;
if (count > 0) {
term->move(term, 0, y1);
for (i = 0; i < count; i++)
tput(tparm(term->TI_dl1));
term->move(term, 0, y2-count+1);
for (i = 0; i < count; i++)
tput(tparm(term->TI_il1));
} else if (count < 0) {
term->move(term, 0, y2+count+1);
for (i = count; i < 0; i++)
tput(tparm(term->TI_dl1));
term->move(term, 0, y1);
for (i = count; i < 0; i++)
tput(tparm(term->TI_il1));
}
}
/* Clear screen (clear_screen / clear) */
static void _clear_screen(TERM_REC *term)
{
tput(tparm(term->TI_clear));
}
/* Clear screen (clr_eos / ed) */
static void _clear_eos(TERM_REC *term)
{
term->move(term, 0, 0);
tput(tparm(term->TI_ed));
}
/* Clear screen (parm_delete_line / dl) */
static void _clear_del(TERM_REC *term)
{
term->move(term, 0, 0);
tput(tparm(term->TI_dl, term->height, term->height));
}
/* Clear screen (delete_line / dl1) */
static void _clear_del_1(TERM_REC *term)
{
int i;
term->move(term, 0, 0);
for (i = 0; i < term->height; i++)
tput(tparm(term->TI_dl1));
}
/* Clear to end of line (clr_eol / el) */
static void _clrtoeol(TERM_REC *term)
{
tput(tparm(term->TI_el));
}
/* Repeat character (rep / rp) */
static void _repeat(TERM_REC *term, char chr, int count)
{
tput(tparm(term->TI_rep, chr, count));
}
/* Repeat character (manual) */
static void _repeat_manual(TERM_REC *term, char chr, int count)
{
while (count > 0) {
putc(chr, term->out);
count--;
}
}
/* Reset all terminal attributes */
static void _set_normal(TERM_REC *term)
{
tput(tparm(term->TI_normal));
}
static void _set_blink(TERM_REC *term)
{
tput(tparm(term->TI_blink));
}
/* Bold on */
static void _set_bold(TERM_REC *term)
{
tput(tparm(term->TI_bold));
}
/* Underline on/off */
static void _set_uline(TERM_REC *term, int set)
{
tput(tparm(set ? term->TI_smul : term->TI_rmul));
}
/* Standout on/off */
static void _set_standout(TERM_REC *term, int set)
{
tput(tparm(set ? term->TI_smso : term->TI_rmso));
}
inline static int color256(const TERM_REC *term, const int color) {
if (color < term->TI_colors)
return color;
if (color < 16)
return color % term->TI_colors;
if (color < 256)
return term_color256map[color] % term->TI_colors;
return color % term->TI_colors;
}
/* Change foreground color */
static void _set_fg(TERM_REC *term, int color)
{
tput(tparm(term->TI_fg[color256(term, color)]));
}
/* Change background color */
static void _set_bg(TERM_REC *term, int color)
{
tput(tparm(term->TI_bg[color256(term, color)]));
}
/* Beep */
static void _beep(TERM_REC *term)
{
tput(tparm(term->TI_bel));
}
static void _ignore(TERM_REC *term)
{
}
static void _ignore_parm(TERM_REC *term, int param)
{
}
static void term_fill_capabilities(TERM_REC *term)
{
int i, ival;
char *sval;
void *ptr;
#ifndef HAVE_TERMINFO
char *tptr = term->buffer2;
#endif
for (i = 0; i < sizeof(tcaps)/sizeof(tcaps[0]); i++) {
ptr = G_STRUCT_MEMBER_P(term, tcaps[i].offset);
switch (tcaps[i].type) {
case CAP_TYPE_FLAG:
ival = term_getflag(tcaps[i]);
*(int *)ptr = ival;
break;
case CAP_TYPE_INT:
ival = term_getnum(tcaps[i]);
*(int *)ptr = ival;
break;
case CAP_TYPE_STR:
sval = term_getstr(tcaps[i], tptr);
if (sval == (char *) -1)
*(char **)ptr = NULL;
else
*(char **)ptr = sval;
break;
}
}
}
static void terminfo_colors_deinit(TERM_REC *term)
{
int i;
if (terminfo_is_colors_set(term)) {
for (i = 0; i < term->TI_colors; i++) {
g_free(term->TI_fg[i]);
g_free(term->TI_bg[i]);
}
g_free_and_null(term->TI_fg);
g_free_and_null(term->TI_bg);
}
}
/* Setup colors - if force is set, use ANSI-style colors if
terminal capabilities don't contain color codes */
void terminfo_setup_colors(TERM_REC *term, int force)
{
static const char ansitab[16] = {
0, 4, 2, 6, 1, 5, 3, 7,
8, 12, 10, 14, 9, 13, 11, 15
};
unsigned int i, color;
terminfo_colors_deinit(term);
if (force && term->TI_setf == NULL && term->TI_setaf == NULL)
term->TI_colors = 8;
if ((term->TI_setf || term->TI_setaf || force) &&
term->TI_colors > 0) {
term->TI_fg = g_new0(char *, term->TI_colors);
term->TI_bg = g_new0(char *, term->TI_colors);
term->set_fg = _set_fg;
term->set_bg = _set_bg;
} else {
/* no colors */
term->TI_colors = 0;
term->set_fg = term->set_bg = _ignore_parm;
}
if (term->TI_setaf) {
for (i = 0; i < term->TI_colors; i++) {
color = i < 16 ? ansitab[i] : i;
term->TI_fg[i] = g_strdup(tparm(term->TI_setaf, color, 0));
}
} else if (term->TI_setf) {
for (i = 0; i < term->TI_colors; i++)
term->TI_fg[i] = g_strdup(tparm(term->TI_setf, i, 0));
} else if (force) {
for (i = 0; i < 8; i++)
term->TI_fg[i] = g_strdup_printf("\033[%dm", 30+ansitab[i]);
}
if (term->TI_setab) {
for (i = 0; i < term->TI_colors; i++) {
color = i < 16 ? ansitab[i] : i;
term->TI_bg[i] = g_strdup(tparm(term->TI_setab, color, 0));
}
} else if (term->TI_setb) {
for (i = 0; i < term->TI_colors; i++)
term->TI_bg[i] = g_strdup(tparm(term->TI_setb, i, 0));
} else if (force) {
for (i = 0; i < 8; i++)
term->TI_bg[i] = g_strdup_printf("\033[%dm", 40+ansitab[i]);
}
}
static void terminfo_input_init(TERM_REC *term)
{
tcgetattr(fileno(term->in), &term->old_tio);
memcpy(&term->tio, &term->old_tio, sizeof(term->tio));
term->tio.c_lflag &= ~(ICANON | ECHO); /* CBREAK, no ECHO */
term->tio.c_cc[VMIN] = 1; /* read() is satisfied after 1 char */
term->tio.c_cc[VTIME] = 0; /* No timer */
/* Disable INTR, QUIT, VDSUSP and SUSP keys */
term->tio.c_cc[VINTR] = _POSIX_VDISABLE;
term->tio.c_cc[VQUIT] = _POSIX_VDISABLE;
#ifdef VDSUSP
term->tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
#endif
#ifdef VSUSP
term->tio.c_cc[VSUSP] = _POSIX_VDISABLE;
#endif
tcsetattr(fileno(term->in), TCSADRAIN, &term->tio);
}
static void terminfo_input_deinit(TERM_REC *term)
{
tcsetattr(fileno(term->in), TCSADRAIN, &term->old_tio);
}
void terminfo_cont(TERM_REC *term)
{
if (term->TI_smcup)
tput(tparm(term->TI_smcup));
terminfo_input_init(term);
}
void terminfo_stop(TERM_REC *term)
{
/* reset colors */
terminfo_set_normal();
/* move cursor to bottom of the screen */
terminfo_move(0, term->height-1);
/* stop cup-mode */
if (term->TI_rmcup)
tput(tparm(term->TI_rmcup));
/* reset input settings */
terminfo_input_deinit(term);
fflush(term->out);
}
static int term_setup(TERM_REC *term)
{
GString *str;
#ifdef HAVE_TERMINFO
int err;
#endif
char *term_env;
term_env = getenv("TERM");
if (term_env == NULL) {
fprintf(stderr, "TERM environment not set\n");
return 0;
}
#ifdef HAVE_TERMINFO
if (setupterm(term_env, 1, &err) != 0) {
fprintf(stderr, "setupterm() failed for TERM=%s: %d\n", term_env, err);
return 0;
}
#else
if (tgetent(term->buffer1, term_env) < 1)
{
fprintf(stderr, "Termcap not found for TERM=%s\n", term_env);
return 0;
}
#endif
term_fill_capabilities(term);
/* Cursor movement */
if (term->TI_cup)
term->move = _move_cup;
else if (term->TI_hpa && term->TI_vpa)
term->move = _move_pa;
else {
fprintf(stderr, "Terminal doesn't support cursor movement\n");
return 0;
}
term->move_relative = _move_relative;
term->set_cursor_visible = term->TI_civis && term->TI_cnorm ?
_set_cursor_visible : _ignore_parm;
/* Scrolling */
if ((term->TI_csr || term->TI_wind) && term->TI_rin && term->TI_indn)
term->scroll = _scroll_region;
else if (term->TI_il && term->TI_dl)
term->scroll = _scroll_line;
else if ((term->TI_csr || term->TI_wind) && term->TI_ri && term->TI_ind)
term->scroll = _scroll_region_1;
else if (term->scroll == NULL && (term->TI_il1 && term->TI_dl1))
term->scroll = _scroll_line_1;
else if (term->scroll == NULL) {
fprintf(stderr, "Terminal doesn't support scrolling\n");
return 0;
}
/* Clearing screen */
if (term->TI_clear)
term->clear = _clear_screen;
else if (term->TI_ed)
term->clear = _clear_eos;
else if (term->TI_dl)
term->clear = _clear_del;
else if (term->TI_dl1)
term->clear = _clear_del_1;
else {
/* we could do this by line inserts as well, but don't
bother - if some terminal has insert line it most probably
has delete line as well, if not a regular clear screen */
fprintf(stderr, "Terminal doesn't support clearing screen\n");
return 0;
}
/* Clearing to end of line */
if (term->TI_el)
term->clrtoeol = _clrtoeol;
else {
fprintf(stderr, "Terminal doesn't support clearing to end of line\n");
return 0;
}
/* Repeating character */
if (term->TI_rep)
term->repeat = _repeat;
else
term->repeat = _repeat_manual;
/* Bold, underline, standout */
term->set_blink = term->TI_blink ? _set_blink : _ignore;
term->set_bold = term->TI_bold ? _set_bold : _ignore;
term->set_uline = term->TI_smul && term->TI_rmul ?
_set_uline : _ignore_parm;
term->set_standout = term->TI_smso && term->TI_rmso ?
_set_standout : _ignore_parm;
/* Create a string to set all attributes off */
str = g_string_new(NULL);
if (term->TI_sgr0)
g_string_append(str, term->TI_sgr0);
if (term->TI_rmul && (term->TI_sgr0 == NULL || strcmp(term->TI_rmul, term->TI_sgr0) != 0))
g_string_append(str, term->TI_rmul);
if (term->TI_rmso && (term->TI_sgr0 == NULL || strcmp(term->TI_rmso, term->TI_sgr0) != 0))
g_string_append(str, term->TI_rmso);
term->TI_normal = str->str;
g_string_free(str, FALSE);
term->set_normal = _set_normal;
term->beep = term->TI_bel ? _beep : _ignore;
terminfo_setup_colors(term, FALSE);
terminfo_cont(term);
return 1;
}
TERM_REC *terminfo_core_init(FILE *in, FILE *out)
{
TERM_REC *old_term, *term;
old_term = current_term;
current_term = term = g_new0(TERM_REC, 1);
term->in = in;
term->out = out;
if (!term_setup(term)) {
g_free(term);
term = NULL;
}
current_term = old_term;
return term;
}
void terminfo_core_deinit(TERM_REC *term)
{
TERM_REC *old_term;
old_term = current_term;
current_term = term;
term->set_normal(term);
current_term = old_term;
terminfo_stop(term);
g_free(term->TI_normal);
terminfo_colors_deinit(term);
g_free(term);
}