#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <inttypes.h>
#include "common.h"
#include "tty.h"
#include "term.h"
#include "stringbuf.h"
#if defined(_WIN32)
#include <windows.h>
#define STDOUT_FILENO 1
#else
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#if defined(__linux__)
#include <linux/kd.h>
#endif
#endif
#define IC_CSI "\x1B["
typedef enum palette_e {
MONOCHROME,
ANSI8,
ANSI16,
ANSI256,
ANSIRGB
} palette_t;
struct term_s {
int fd_out;
ssize_t width;
ssize_t height;
ssize_t raw_enabled;
bool nocolor;
bool silent;
bool is_utf8;
attr_t attr;
palette_t palette;
buffer_mode_t bufmode;
stringbuf_t* buf;
tty_t* tty;
alloc_t* mem;
#ifdef _WIN32
HANDLE hcon;
WORD hcon_default_attr;
WORD hcon_orig_attr;
DWORD hcon_orig_mode;
DWORD hcon_mode;
UINT hcon_orig_cp;
COORD hcon_save_cursor;
#endif
};
static bool term_write_direct(term_t* term, const char* s, ssize_t n );
static void term_append_buf(term_t* term, const char* s, ssize_t n);
#include "term_color.c"
ic_private void term_left(term_t* term, ssize_t n) {
if (n <= 0) return;
term_writef( term, IC_CSI "%zdD", n );
}
ic_private void term_right(term_t* term, ssize_t n) {
if (n <= 0) return;
term_writef( term, IC_CSI "%zdC", n );
}
ic_private void term_up(term_t* term, ssize_t n) {
if (n <= 0) return;
term_writef( term, IC_CSI "%zdA", n );
}
ic_private void term_down(term_t* term, ssize_t n) {
if (n <= 0) return;
term_writef( term, IC_CSI "%zdB", n );
}
ic_private void term_clear_line(term_t* term) {
term_write( term, "\r" IC_CSI "K");
}
ic_private void term_clear_to_end_of_line(term_t* term) {
term_write(term, IC_CSI "K");
}
ic_private void term_start_of_line(term_t* term) {
term_write( term, "\r" );
}
ic_private ssize_t term_get_width(term_t* term) {
return term->width;
}
ic_private ssize_t term_get_height(term_t* term) {
return term->height;
}
ic_private void term_attr_reset(term_t* term) {
term_write(term, IC_CSI "m" );
}
ic_private void term_underline(term_t* term, bool on) {
term_write(term, on ? IC_CSI "4m" : IC_CSI "24m" );
}
ic_private void term_reverse(term_t* term, bool on) {
term_write(term, on ? IC_CSI "7m" : IC_CSI "27m");
}
ic_private void term_bold(term_t* term, bool on) {
term_write(term, on ? IC_CSI "1m" : IC_CSI "22m" );
}
ic_private void term_italic(term_t* term, bool on) {
term_write(term, on ? IC_CSI "3m" : IC_CSI "23m" );
}
ic_private void term_writeln(term_t* term, const char* s) {
term_write(term,s);
term_write(term,"\n");
}
ic_private void term_write_char(term_t* term, char c) {
char buf[2];
buf[0] = c;
buf[1] = 0;
term_write_n(term, buf, 1 );
}
ic_private attr_t term_get_attr( const term_t* term ) {
return term->attr;
}
ic_private void term_set_attr( term_t* term, attr_t attr ) {
if (term->nocolor) return;
if (attr.x.color != term->attr.x.color && attr.x.color != IC_COLOR_NONE) {
term_color(term,attr.x.color);
if (term->palette < ANSIRGB && color_is_rgb(attr.x.color)) {
term->attr.x.color = attr.x.color;
}
}
if (attr.x.bgcolor != term->attr.x.bgcolor && attr.x.bgcolor != IC_COLOR_NONE) {
term_bgcolor(term,attr.x.bgcolor);
if (term->palette < ANSIRGB && color_is_rgb(attr.x.bgcolor)) {
term->attr.x.bgcolor = attr.x.bgcolor;
}
}
if (attr.x.bold != term->attr.x.bold && attr.x.bold != IC_NONE) {
term_bold(term,attr.x.bold == IC_ON);
}
if (attr.x.underline != term->attr.x.underline && attr.x.underline != IC_NONE) {
term_underline(term,attr.x.underline == IC_ON);
}
if (attr.x.reverse != term->attr.x.reverse && attr.x.reverse != IC_NONE) {
term_reverse(term,attr.x.reverse == IC_ON);
}
if (attr.x.italic != term->attr.x.italic && attr.x.italic != IC_NONE) {
term_italic(term,attr.x.italic == IC_ON);
}
assert(attr.x.color == term->attr.x.color || attr.x.color == IC_COLOR_NONE);
assert(attr.x.bgcolor == term->attr.x.bgcolor || attr.x.bgcolor == IC_COLOR_NONE);
assert(attr.x.bold == term->attr.x.bold || attr.x.bold == IC_NONE);
assert(attr.x.reverse == term->attr.x.reverse || attr.x.reverse == IC_NONE);
assert(attr.x.underline == term->attr.x.underline || attr.x.underline == IC_NONE);
assert(attr.x.italic == term->attr.x.italic || attr.x.italic == IC_NONE);
}
ic_private void term_writef(term_t* term, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
term_vwritef(term,fmt,ap);
va_end(ap);
}
ic_private void term_vwritef(term_t* term, const char* fmt, va_list args ) {
sbuf_append_vprintf(term->buf, fmt, args);
}
ic_private void term_write_formatted( term_t* term, const char* s, const attr_t* attrs ) {
term_write_formatted_n( term, s, attrs, ic_strlen(s));
}
ic_private void term_write_formatted_n( term_t* term, const char* s, const attr_t* attrs, ssize_t len ) {
if (attrs == NULL) {
term_write(term,s);
}
else {
if (term->raw_enabled <= 0) {
term_start_raw(term);
}
const attr_t default_attr = term_get_attr(term);
attr_t attr = attr_none();
ssize_t i = 0;
ssize_t n = 0;
while( i+n < len && s[i+n] != 0 ) {
if (!attr_is_eq(attr,attrs[i+n])) {
if (n > 0) {
term_write_n( term, s+i, n );
i += n;
n = 0;
}
attr = attrs[i];
term_set_attr( term, attr_update_with(default_attr,attr) );
}
n++;
}
if (n > 0) {
term_write_n( term, s+i, n );
i += n;
n = 0;
}
assert(s[i] != 0 || i == len);
term_set_attr(term, default_attr);
}
}
ic_private void term_beep(term_t* term) {
if (term->silent) return;
fprintf(stderr,"\x7");
fflush(stderr);
}
ic_private void term_write_repeat(term_t* term, const char* s, ssize_t count) {
for (; count > 0; count--) {
term_write(term, s);
}
}
ic_private void term_write(term_t* term, const char* s) {
if (s == NULL || s[0] == 0) return;
ssize_t n = ic_strlen(s);
term_write_n(term,s,n);
}
ic_private void term_write_n(term_t* term, const char* s, ssize_t n) {
if (s == NULL || n <= 0) return;
term_append_buf(term, s, n);
}
ic_private void term_flush(term_t* term) {
if (sbuf_len(term->buf) > 0) {
term_write_direct(term, sbuf_string(term->buf), sbuf_len(term->buf));
sbuf_clear(term->buf);
}
}
ic_private buffer_mode_t term_set_buffer_mode(term_t* term, buffer_mode_t mode) {
buffer_mode_t oldmode = term->bufmode;
if (oldmode != mode) {
if (mode == UNBUFFERED) {
term_flush(term);
}
term->bufmode = mode;
}
return oldmode;
}
static void term_check_flush(term_t* term, bool contains_nl) {
if (term->bufmode == UNBUFFERED ||
sbuf_len(term->buf) > 4000 ||
(term->bufmode == LINEBUFFERED && contains_nl))
{
term_flush(term);
}
}
static void term_init_raw(term_t* term);
ic_private term_t* term_new(alloc_t* mem, tty_t* tty, bool nocolor, bool silent, int fd_out )
{
term_t* term = mem_zalloc_tp(mem, term_t);
if (term == NULL) return NULL;
term->fd_out = (fd_out < 0 ? STDOUT_FILENO : fd_out);
term->nocolor = nocolor || (isatty(term->fd_out) == 0);
term->silent = silent;
term->mem = mem;
term->tty = tty;
term->width = 80;
term->height = 25;
term->is_utf8 = tty_is_utf8(tty);
term->palette = ANSI16;
term->buf = sbuf_new(mem);
term->bufmode = LINEBUFFERED;
term->attr = attr_default();
if (getenv("NO_COLOR") != NULL) {
term->nocolor = true;
}
if (!term->nocolor) {
const char* colorterm = getenv("COLORTERM");
const char* eterm = getenv("TERM");
if (ic_contains(colorterm,"24bit") || ic_contains(colorterm,"truecolor") || ic_contains(colorterm,"direct")) {
term->palette = ANSIRGB;
}
else if (ic_contains(colorterm,"8bit") || ic_contains(colorterm,"256color")) { term->palette = ANSI256; }
else if (ic_contains(colorterm,"4bit") || ic_contains(colorterm,"16color")) { term->palette = ANSI16; }
else if (ic_contains(colorterm,"3bit") || ic_contains(colorterm,"8color")) { term->palette = ANSI8; }
else if (ic_contains(colorterm,"1bit") || ic_contains(colorterm,"nocolor") || ic_contains(colorterm,"monochrome")) {
term->palette = MONOCHROME;
}
else if (getenv("WT_SESSION") != NULL) { term->palette = ANSIRGB; }
else if (getenv("ITERM_SESSION_ID") != NULL) { term->palette = ANSIRGB; }
else if (getenv("VSCODE_PID") != NULL) { term->palette = ANSIRGB; }
else {
if (ic_contains(eterm,"truecolor") || ic_contains(eterm,"direct") || ic_contains(colorterm,"24bit")) {
term->palette = ANSIRGB;
}
else if (ic_contains(eterm,"alacritty") || ic_contains(eterm,"kitty")) {
term->palette = ANSIRGB;
}
else if (ic_contains(eterm,"256color") || ic_contains(eterm,"gnome")) {
term->palette = ANSI256;
}
else if (ic_contains(eterm,"16color")){ term->palette = ANSI16; }
else if (ic_contains(eterm,"8color")) { term->palette = ANSI8; }
else if (ic_contains(eterm,"monochrome") || ic_contains(eterm,"nocolor") || ic_contains(eterm,"dumb")) {
term->palette = MONOCHROME;
}
}
debug_msg("term: color-bits: %d (COLORTERM=%s, TERM=%s)\n", term_get_color_bits(term), colorterm, eterm);
}
const char* env_columns = getenv("COLUMNS");
if (env_columns != NULL) { ic_atoz(env_columns, &term->width); }
const char* env_lines = getenv("LINES");
if (env_lines != NULL) { ic_atoz(env_lines, &term->height); }
term_init_raw(term);
term_update_dim(term);
term_attr_reset(term);
return term;
}
ic_private bool term_is_interactive(const term_t* term) {
ic_unused(term);
const char* eterm = getenv("TERM");
debug_msg("term: TERM=%s\n", eterm);
if (eterm != NULL &&
(strstr("dumb|DUMB|cons25|CONS25|emacs|EMACS",eterm) != NULL)) {
return false;
}
return true;
}
ic_private bool term_enable_beep(term_t* term, bool enable) {
bool prev = term->silent;
term->silent = !enable;
return prev;
}
ic_private bool term_enable_color(term_t* term, bool enable) {
bool prev = !term->nocolor;
term->nocolor = !enable;
return prev;
}
ic_private void term_free(term_t* term) {
if (term == NULL) return;
term_flush(term);
term_end_raw(term, true);
sbuf_free(term->buf); term->buf = NULL;
mem_free(term->mem, term);
}
static void term_append_esc(term_t* term, const char* const s, ssize_t len) {
if (s[1]=='[' && s[len-1] == 'm') {
if (term->nocolor) return;
term->attr = attr_update_with(term->attr, attr_from_esc_sgr(s,len));
}
sbuf_append_n(term->buf, s, len);
}
static void term_append_utf8(term_t* term, const char* s, ssize_t len) {
ssize_t nread;
unicode_t uchr = unicode_from_qutf8((const uint8_t*)s, len, &nread);
uint8_t c;
if (unicode_is_raw(uchr, &c)) {
sbuf_append_char(term->buf,(char)c);
}
else if (!term->is_utf8) {
sbuf_append_n(term->buf, s, len);
}
else {
sbuf_append_n(term->buf, s, len);
}
}
static void term_append_buf( term_t* term, const char* s, ssize_t len ) {
ssize_t pos = 0;
bool newline = false;
while (pos < len) {
ssize_t ascii = 0;
ssize_t next;
while ((next = str_next_ofs(s, len, pos+ascii, NULL)) > 0 &&
(uint8_t)s[pos + ascii] > '\x1B' && (uint8_t)s[pos + ascii] <= 0x7F )
{
ascii += next;
}
if (ascii > 0) {
sbuf_append_n(term->buf, s+pos, ascii);
pos += ascii;
}
if (next <= 0) break;
const uint8_t c = (uint8_t)s[pos];
if (c >= 0x80) {
term_append_utf8(term, s+pos, next);
}
else if (next > 1 && c == '\x1B') {
term_append_esc(term, s+pos, next);
}
else if (c < ' ' && c != 0 && (c < '\x07' || c > '\x0D')) {
}
else {
if (c == '\n') { newline = true; }
sbuf_append_n(term->buf, s+pos, next);
}
pos += next;
}
term_check_flush(term, newline);
}
#if !defined(_WIN32)
static bool term_write_direct(term_t* term, const char* s, ssize_t n) {
ssize_t count = 0;
while( count < n ) {
ssize_t nwritten = write(term->fd_out, s + count, to_size_t(n - count));
if (nwritten > 0) {
count += nwritten;
}
else if (errno != EINTR && errno != EAGAIN) {
debug_msg("term: write failed: length %i, errno %i: \"%s\"\n", n, errno, s);
return false;
}
}
return true;
}
#else
#if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0)
#endif
#if !defined(ENABLE_LVB_GRID_WORLDWIDE)
#define ENABLE_LVB_GRID_WORLDWIDE (0)
#endif
static bool term_write_console(term_t* term, const char* s, ssize_t n ) {
DWORD written;
WriteFile(term->hcon, s, (DWORD)(to_size_t(n)), &written, NULL);
return (written == (DWORD)(to_size_t(n)));
}
static bool term_get_cursor_pos( term_t* term, ssize_t* row, ssize_t* col) {
*row = 0;
*col = 0;
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return false;
*row = (ssize_t)info.dwCursorPosition.Y + 1;
*col = (ssize_t)info.dwCursorPosition.X + 1;
return true;
}
static void term_move_cursor_to( term_t* term, ssize_t row, ssize_t col ) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
if (col > info.dwSize.X) col = info.dwSize.X;
if (row > info.dwSize.Y) row = info.dwSize.Y;
if (col <= 0) col = 1;
if (row <= 0) row = 1;
COORD coord;
coord.X = (SHORT)col - 1;
coord.Y = (SHORT)row - 1;
SetConsoleCursorPosition( term->hcon, coord);
}
static void term_cursor_save(term_t* term) {
memset(&term->hcon_save_cursor, 0, sizeof(term->hcon_save_cursor));
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return;
term->hcon_save_cursor = info.dwCursorPosition;
}
static void term_cursor_restore(term_t* term) {
if (term->hcon_save_cursor.X == 0) return;
SetConsoleCursorPosition(term->hcon, term->hcon_save_cursor);
}
static void term_move_cursor( term_t* term, ssize_t drow, ssize_t dcol, ssize_t n ) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
COORD cur = info.dwCursorPosition;
ssize_t col = (ssize_t)cur.X + 1 + n*dcol;
ssize_t row = (ssize_t)cur.Y + 1 + n*drow;
term_move_cursor_to( term, row, col );
}
static void term_cursor_visible( term_t* term, bool visible ) {
CONSOLE_CURSOR_INFO info;
if (!GetConsoleCursorInfo(term->hcon,&info)) return;
info.bVisible = visible;
SetConsoleCursorInfo(term->hcon,&info);
}
static void term_erase_line( term_t* term, ssize_t mode ) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
DWORD written;
COORD start;
ssize_t length;
if (mode == 2) {
start.X = 0;
start.Y = info.dwCursorPosition.Y;
length = (ssize_t)info.srWindow.Right + 1;
}
else if (mode == 1) {
start.X = 0;
start.Y = info.dwCursorPosition.Y;
length = info.dwCursorPosition.X;
}
else {
length = (ssize_t)info.srWindow.Right - info.dwCursorPosition.X + 1;
start = info.dwCursorPosition;
}
FillConsoleOutputAttribute( term->hcon, term->hcon_default_attr, (DWORD)length, start, &written );
FillConsoleOutputCharacterA( term->hcon, ' ', (DWORD)length, start, &written );
}
static void term_clear_screen(term_t* term, ssize_t mode) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return;
COORD start;
start.X = 0;
start.Y = 0;
ssize_t length;
ssize_t width = (ssize_t)info.dwSize.X;
if (mode == 2) {
length = width * info.dwSize.Y;
}
else if (mode == 1) {
length = (width * ((ssize_t)info.dwCursorPosition.Y - 1)) + info.dwCursorPosition.X;
}
else {
start = info.dwCursorPosition;
length = (width * ((ssize_t)info.dwSize.Y - info.dwCursorPosition.Y)) + (width - info.dwCursorPosition.X + 1);
}
DWORD written;
FillConsoleOutputAttribute(term->hcon, term->hcon_default_attr, (DWORD)length, start, &written);
FillConsoleOutputCharacterA(term->hcon, ' ', (DWORD)length, start, &written);
}
static WORD attr_color[8] = {
0,
FOREGROUND_RED,
FOREGROUND_GREEN,
FOREGROUND_RED | FOREGROUND_GREEN,
FOREGROUND_BLUE,
FOREGROUND_RED | FOREGROUND_BLUE,
FOREGROUND_GREEN | FOREGROUND_BLUE,
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
};
static void term_set_win_attr( term_t* term, attr_t ta ) {
WORD def_attr = term->hcon_default_attr;
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
WORD cur_attr = info.wAttributes;
WORD attr = cur_attr;
if (ta.x.color != IC_COLOR_NONE) {
if (ta.x.color >= IC_ANSI_BLACK && ta.x.color <= IC_ANSI_SILVER) {
attr = (attr & 0xFFF0) | attr_color[ta.x.color - IC_ANSI_BLACK];
}
else if (ta.x.color >= IC_ANSI_GRAY && ta.x.color <= IC_ANSI_WHITE) {
attr = (attr & 0xFFF0) | attr_color[ta.x.color - IC_ANSI_GRAY] | FOREGROUND_INTENSITY;
}
else if (ta.x.color == IC_ANSI_DEFAULT) {
attr = (attr & 0xFFF0) | (def_attr & 0x000F);
}
}
if (ta.x.bgcolor != IC_COLOR_NONE) {
if (ta.x.bgcolor >= IC_ANSI_BLACK && ta.x.bgcolor <= IC_ANSI_SILVER) {
attr = (attr & 0xFF0F) | (WORD)(attr_color[ta.x.bgcolor - IC_ANSI_BLACK] << 4);
}
else if (ta.x.bgcolor >= IC_ANSI_GRAY && ta.x.bgcolor <= IC_ANSI_WHITE) {
attr = (attr & 0xFF0F) | (WORD)(attr_color[ta.x.bgcolor - IC_ANSI_GRAY] << 4) | BACKGROUND_INTENSITY;
}
else if (ta.x.bgcolor == IC_ANSI_DEFAULT) {
attr = (attr & 0xFF0F) | (def_attr & 0x00F0);
}
}
if (ta.x.underline != IC_NONE) {
attr = (attr & ~COMMON_LVB_UNDERSCORE) | (ta.x.underline == IC_ON ? COMMON_LVB_UNDERSCORE : 0);
}
if (ta.x.reverse != IC_NONE) {
attr = (attr & ~COMMON_LVB_REVERSE_VIDEO) | (ta.x.reverse == IC_ON ? COMMON_LVB_REVERSE_VIDEO : 0);
}
if (attr != cur_attr) {
SetConsoleTextAttribute(term->hcon, attr);
}
}
static ssize_t esc_param( const char* s, ssize_t def ) {
if (*s == '?') s++;
ssize_t n = def;
ic_atoz(s, &n);
return n;
}
static void esc_param2( const char* s, ssize_t* p1, ssize_t* p2, ssize_t def ) {
if (*s == '?') s++;
*p1 = def;
*p2 = def;
ic_atoz2(s, p1, p2);
}
static void term_write_esc( term_t* term, const char* s, ssize_t len ) {
ssize_t row;
ssize_t col;
if (s[1] == '[') {
switch (s[len-1]) {
case 'A':
term_move_cursor(term, -1, 0, esc_param(s+2, 1));
break;
case 'B':
term_move_cursor(term, 1, 0, esc_param(s+2, 1));
break;
case 'C':
term_move_cursor(term, 0, 1, esc_param(s+2, 1));
break;
case 'D':
term_move_cursor(term, 0, -1, esc_param(s+2, 1));
break;
case 'H':
esc_param2(s+2, &row, &col, 1);
term_move_cursor_to(term, row, col);
break;
case 'K':
term_erase_line(term, esc_param(s+2, 0));
break;
case 'm':
term_set_win_attr( term, attr_from_esc_sgr(s,len) );
break;
case 'E':
term_get_cursor_pos(term, &row, &col);
row += esc_param(s+2, 1);
term_move_cursor_to(term, row, 1);
break;
case 'F':
term_get_cursor_pos(term, &row, &col);
row -= esc_param(s+2, 1);
term_move_cursor_to(term, row, 1);
break;
case 'G':
term_get_cursor_pos(term, &row, &col);
col = esc_param(s+2, 1);
term_move_cursor_to(term, row, col);
break;
case 'J':
term_clear_screen(term, esc_param(s+2, 0));
break;
case 'h':
if (strncmp(s+2, "?25h", 4) == 0) {
term_cursor_visible(term, true);
}
break;
case 'l':
if (strncmp(s+2, "?25l", 4) == 0) {
term_cursor_visible(term, false);
}
break;
case 's':
term_cursor_save(term);
break;
case 'u':
term_cursor_restore(term);
break;
}
}
else if (s[1] == '7') {
term_cursor_save(term);
}
else if (s[1] == '8') {
term_cursor_restore(term);
}
else {
}
}
static bool term_write_direct(term_t* term, const char* s, ssize_t len ) {
term_cursor_visible(term,false);
ssize_t pos = 0;
if ((term->hcon_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
term_write_console(term, s, len);
pos = len;
}
else {
while( pos < len ) {
ssize_t nonctrl = 0;
ssize_t next;
while( (next = str_next_ofs( s, len, pos+nonctrl, NULL )) > 0 &&
(uint8_t)s[pos + nonctrl] >= ' ' && (uint8_t)s[pos + nonctrl] <= 0x7F) {
nonctrl += next;
}
if (nonctrl > 0) {
term_write_console(term, s+pos, nonctrl);
pos += nonctrl;
}
if (next <= 0) break;
if ((uint8_t)s[pos] >= 0x80) {
term_write_console(term, s+pos, next);
}
else if (next > 1 && s[pos] == '\x1B') {
term_write_esc(term, s+pos, next);
}
else if (next == 1 && (s[pos] == '\r' || s[pos] == '\n' || s[pos] == '\t' || s[pos] == '\b')) {
term_write_console( term, s+pos, next);
}
else {
}
pos += next;
}
}
term_cursor_visible(term,true);
assert(pos == len);
return (pos == len);
}
#endif
#if !defined(_WIN32)
static bool term_esc_query_raw( term_t* term, const char* query, char* buf, ssize_t buflen )
{
if (buf==NULL || buflen <= 0 || query[0] == 0) return false;
bool osc = (query[1] == ']');
if (!term_write_direct(term, query, ic_strlen(query))) return false;
debug_msg("term: read tty query response to: ESC %s\n", query + 1);
return tty_read_esc_response( term->tty, query[1], osc, buf, buflen );
}
static bool term_esc_query( term_t* term, const char* query, char* buf, ssize_t buflen )
{
if (!tty_start_raw(term->tty)) return false;
bool ok = term_esc_query_raw(term,query,buf,buflen);
tty_end_raw(term->tty);
return ok;
}
static bool term_get_cursor_pos( term_t* term, ssize_t* row, ssize_t* col)
{
char buf[128];
if (!term_esc_query(term,"\x1B[6n",buf,128)) return false;
if (!ic_atoz2(buf,row,col)) return false;
return true;
}
static void term_set_cursor_pos( term_t* term, ssize_t row, ssize_t col ) {
term_writef( term, IC_CSI "%zd;%zdH", row, col );
}
ic_private bool term_update_dim(term_t* term) {
ssize_t cols = 0;
ssize_t rows = 0;
struct winsize ws;
if (ioctl(term->fd_out, TIOCGWINSZ, &ws) >= 0) {
cols = ws.ws_col;
rows = ws.ws_row;
}
else {
debug_msg("term: ioctl term-size failed: %d,%d\n", ws.ws_row, ws.ws_col);
ssize_t col0 = 0;
ssize_t row0 = 0;
if (term_get_cursor_pos(term,&row0,&col0)) {
term_set_cursor_pos(term,999,999);
ssize_t col1 = 0;
ssize_t row1 = 0;
if (term_get_cursor_pos(term,&row1,&col1)) {
cols = col1;
rows = row1;
}
term_set_cursor_pos(term,row0,col0);
}
else {
}
}
bool changed = (term->width != cols || term->height != rows);
debug_msg("terminal dim: %zd,%zd: %s\n", rows, cols, changed ? "changed" : "unchanged");
if (cols > 0) {
term->width = cols;
term->height = rows;
}
return changed;
}
#else
ic_private bool term_update_dim(term_t* term) {
if (term->hcon == 0) {
term->hcon = GetConsoleWindow();
}
ssize_t rows = 0;
ssize_t cols = 0;
CONSOLE_SCREEN_BUFFER_INFO sbinfo;
if (GetConsoleScreenBufferInfo(term->hcon, &sbinfo)) {
cols = (ssize_t)sbinfo.srWindow.Right - (ssize_t)sbinfo.srWindow.Left + 1;
rows = (ssize_t)sbinfo.srWindow.Bottom - (ssize_t)sbinfo.srWindow.Top + 1;
}
bool changed = (term->width != cols || term->height != rows);
term->width = cols;
term->height = rows;
debug_msg("term: update dim: %zd, %zd\n", term->height, term->width );
return changed;
}
#endif
#if !defined(_WIN32)
ic_private void term_start_raw(term_t* term) {
term->raw_enabled++;
}
ic_private void term_end_raw(term_t* term, bool force) {
if (term->raw_enabled <= 0) return;
if (!force) {
term->raw_enabled--;
}
else {
term->raw_enabled = 0;
}
}
static bool term_esc_query_color_raw(term_t* term, int color_idx, uint32_t* color ) {
char buf[128+1];
snprintf(buf,128,"\x1B]4;%d;?\x1B\\", color_idx);
if (!term_esc_query_raw( term, buf, buf, 128 )) {
debug_msg("esc query response not received\n");
return false;
}
if (buf[0] != '4') return false;
const char* rgb = strchr(buf,':');
if (rgb==NULL) return false;
rgb++;
unsigned int r,g,b;
if (sscanf(rgb,"%x/%x/%x",&r,&g,&b) != 3) return false;
if (rgb[2]!='/') {
r = (r+0x7F)/0x100;
g = (g+0x7F)/0x100;
b = (b+0x7F)/0x100;
}
*color = (ic_cap8(r)<<16) | (ic_cap8(g)<<8) | ic_cap8(b);
debug_msg("color query: %02x,%02x,%02x: %06x\n", r, g, b, *color);
return true;
}
static void term_update_ansi16(term_t* term) {
debug_msg("update ansi colors\n");
#if defined(GIO_CMAP)
uint8_t cmap[48];
memset(cmap,0,48);
if (ioctl(term->fd_out,GIO_CMAP,&cmap) >= 0) {
for(ssize_t i = 0; i < 48; i+=3) {
uint32_t color = ((uint32_t)(cmap[i]) << 16) | ((uint32_t)(cmap[i+1]) << 8) | cmap[i+2];
debug_msg("term (ioctl) ansi color %d: 0x%06x\n", i, color);
ansi256[i] = color;
}
return;
}
else {
debug_msg("ioctl GIO_CMAP failed: entry 1: 0x%02x%02x%02x\n", cmap[3], cmap[4], cmap[5]);
}
#endif
#if __APPLE__
if (tty_start_raw(term->tty)) {
for(ssize_t i = 0; i < 16; i++) {
uint32_t color;
if (!term_esc_query_color_raw(term, i, &color)) break;
debug_msg("term ansi color %d: 0x%06x\n", i, color);
ansi256[i] = color;
}
tty_end_raw(term->tty);
}
#endif
}
static void term_init_raw(term_t* term) {
if (term->palette < ANSIRGB) {
term_update_ansi16(term);
}
}
#else
ic_private void term_start_raw(term_t* term) {
if (term->raw_enabled++ > 0) return;
CONSOLE_SCREEN_BUFFER_INFO info;
if (GetConsoleScreenBufferInfo(term->hcon, &info)) {
term->hcon_orig_attr = info.wAttributes;
}
term->hcon_orig_cp = GetConsoleOutputCP();
SetConsoleOutputCP(CP_UTF8);
if (term->hcon_mode == 0) {
DWORD mode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_LVB_GRID_WORLDWIDE;
if (term->palette >= ANSI256 && SetConsoleMode(term->hcon, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
term->hcon_mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
debug_msg("term: console mode: virtual terminal processing enabled\n");
}
else if (SetConsoleMode(term->hcon, mode)) {
term->hcon_mode = mode;
term->palette = ANSI16;
}
GetConsoleMode(term->hcon, &mode);
debug_msg("term: console mode: orig: 0x%x, new: 0x%x, current 0x%x\n", term->hcon_orig_mode, term->hcon_mode, mode);
}
else {
SetConsoleMode(term->hcon, term->hcon_mode);
}
}
ic_private void term_end_raw(term_t* term, bool force) {
if (term->raw_enabled <= 0) return;
if (!force && term->raw_enabled > 1) {
term->raw_enabled--;
}
else {
term->raw_enabled = 0;
SetConsoleMode(term->hcon, term->hcon_orig_mode);
SetConsoleOutputCP(term->hcon_orig_cp);
SetConsoleTextAttribute(term->hcon, term->hcon_orig_attr);
}
}
static void term_init_raw(term_t* term) {
term->hcon = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(term->hcon, &term->hcon_orig_mode);
CONSOLE_SCREEN_BUFFER_INFOEX info;
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(info);
if (GetConsoleScreenBufferInfoEx(term->hcon, &info)) {
term->hcon_default_attr = info.wAttributes;
for (unsigned i = 0; i < 16; i++) {
COLORREF cr = info.ColorTable[i];
uint32_t color = (ic_cap8(GetRValue(cr))<<16) | (ic_cap8(GetGValue(cr))<<8) | ic_cap8(GetBValue(cr));
unsigned j = (i&0x08) | ((i&0x04)>>2) | (i&0x02) | (i&0x01)<<2;
debug_msg("term: ansi color %d is 0x%06x\n", j, color);
ansi256[j] = color;
}
}
else {
DWORD err = GetLastError();
debug_msg("term: cannot get console screen buffer: %d %x", err, err);
}
term_start_raw(term);
term_end_raw(term,false);
}
#endif