#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <sixel.h>
struct canvas {
int width;
int height;
unsigned char *buf;
int x0;
int y0;
int x1;
int y1;
};
static int
canvas_init(struct canvas *c, int width, int height)
{
c->width = width;
c->height = height;
c->x1 = (-1);
c->y1 = (-1);
c->x0 = (-1);
c->y0 = (-1);
c->buf = calloc(1, width * height);
return 0;
}
static void
canvas_deinit(struct canvas *c)
{
free(c->buf);
}
static void
canvas_point(struct canvas *c, int x, int y)
{
c->x1 = x;
c->y1 = y;
}
static void
canvas_sync(struct canvas *c)
{
c->x0 = c->x1;
c->y0 = c->y1;
}
static int
wait_stdin(int usec)
{
fd_set rfds;
struct timeval tv;
tv.tv_sec = usec / 1000000;
tv.tv_usec = usec % 1000000;
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
return select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv);
}
static int
scroll_on_demand(int pixelheight)
{
struct winsize size = {0, 0, 0, 0};
int row = 0;
int col = 0;
int cellheight;
int scroll;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
if (size.ws_ypixel <= 0) {
printf("\033[H\0337");
return 0;
}
printf("\033[6n");
if (wait_stdin(1000 * 1000) < 0)
goto err;
if (scanf("\033[%d;%dR", &row, &col) != 2)
goto err;
cellheight = pixelheight * size.ws_row / size.ws_ypixel + 1;
scroll = cellheight + row - size.ws_row;
printf("\033[%dS\033[%dA", scroll, scroll);
printf("\0337");
return size.ws_ypixel / size.ws_row * (row - (scroll < 0 ? 0: scroll) - 1);
err:
printf("\033[H\0337");
return 0;
}
static int tty_raw(struct termios *old_termios)
{
struct termios new_termios;
int ret;
ret = tcgetattr(STDIN_FILENO, old_termios);
if (ret < 0)
return ret;
memcpy(&new_termios, old_termios, sizeof(*old_termios));
new_termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
new_termios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
new_termios.c_cflag &= ~(CSIZE | PARENB);
new_termios.c_cflag |= CS8;
new_termios.c_oflag &= ~(OPOST);
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios);
if (ret < 0)
return ret;
printf("\033[?25l\0338");
return 0;
}
static void tty_restore(struct termios *old_termios)
{
(void) tcsetattr(STDIN_FILENO, TCSADRAIN, old_termios);
}
static int sixel_write(char *data, int size, void *priv)
{
return fwrite(data, 1, size, (FILE *)priv);
}
static int draw_line(struct canvas const *c)
{
int dx;
int dy;
int sx;
int sy;
int err;
int err2;
int x0;
int y0;
int x1;
int y1;
if (c->x0 < 0 || c->y0 < 0 || c->x0 >= c->width || c->y0 >= c->height)
return 1;
x0 = c->x0;
y0 = c->y0;
x1 = c->x1;
y1 = c->y1;
if (x1 < 0)
x1 = 0;
if (y1 < 0)
y1 = 0;
if (x1 >= c->width)
x1 = c->width - 1;
if (y1 >= c->height)
y1 = c->height - 1;
if (x0 == x1 && y0 == y1)
return 1;
dx = x0 > x1 ? x0 - x1 : x1 - x0;
dy = y0 > y1 ? y0 - y1 : y1 - y0;
sx = x0 < x1 ? 1 : -1;
sy = y0 < y1 ? 1 : -1;
err = dx - dy;
for (;;) {
c->buf[c->width * y0 + x0] = 0xff;
if (x0 == x1 && y0 == y1)
break;
err2 = 2 * err;
if (err2 > - dy) {
err -= dy;
x0 += sx;
}
if (err2 < dx) {
err += dx;
y0 += sy;
}
}
return 0;
}
enum {
STATE_GROUND = 0,
STATE_ESC = 1,
STATE_ESC_INTERMEDIATE = 2,
STATE_CSI_PARAMETER = 3,
STATE_CSI_INTERMEDIATE = 4,
STATE_SS = 5,
STATE_OSC = 6,
STATE_STR = 7
};
int main(int argc, char **argv)
{
SIXELSTATUS status = SIXEL_FALSE;
struct canvas c;
sixel_output_t *output = NULL;
sixel_dither_t *dither = NULL;
struct termios old_termios;
unsigned char ibuf[256];
unsigned char *p;
int n;
unsigned int params[1024];
int param = 0;
int params_idx = 0;
int ibytes;
int state;
int offset;
printf("\033[?1;3;256S" "\033[?8452h" "\033[1;1'z");
(void) tty_raw(&old_termios);
if (canvas_init(&c, 640, 480) != 0)
return (-1);
offset = scroll_on_demand(c.height);
status = sixel_output_new(&output, sixel_write, stdout, NULL);
if (SIXEL_FAILED(status))
goto end;
dither = sixel_dither_get(SIXEL_BUILTIN_G8);
sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_G8);
status = sixel_encode(c.buf, c.width, c.height, 0, dither, output);
if (SIXEL_FAILED(status))
goto end;
for (state = STATE_GROUND;;) {
printf("\033['|");
fflush(stdout);
if (wait_stdin(10000) < 0)
goto end;
n = read(STDIN_FILENO, ibuf, sizeof(ibuf));
for (p = ibuf; p < ibuf + n; ++p) {
switch (state) {
case STATE_GROUND:
switch (*p) {
case 0x03:
if (wait_stdin(10000) >= 0)
n = read(STDIN_FILENO, ibuf, sizeof(ibuf));
goto end;
break;
case 0x1b:
state = STATE_ESC;
break;
default:
break;
}
break;
case STATE_ESC:
switch (*p) {
case 0x5b:
ibytes = 0;
param = 0;
params_idx = 0;
state = STATE_CSI_PARAMETER;
break;
case 0x5d:
state = STATE_OSC;
break;
case 0x4e ... 0x4f:
state = STATE_SS;
break;
case 0x50:
case 0x58:
case 0x5e ... 0x5f:
state = STATE_STR;
break;
case 0x1b:
state = STATE_ESC;
break;
case 0x18:
case 0x1a:
state = STATE_GROUND;
break;
case 0x00 ... 0x17:
case 0x19:
case 0x1c ... 0x1f:
case 0x7f:
break;
case 0x20 ... 0x2f:
ibytes = ibytes << 8 | *p;
state = STATE_ESC_INTERMEDIATE;
break;
default:
state = STATE_GROUND;
break;
}
break;
case STATE_CSI_PARAMETER:
switch (*p) {
case 0x00 ... 0x17:
case 0x19:
case 0x1c ... 0x1f:
case 0x7f:
break;
case 0x18:
case 0x1a:
state = STATE_GROUND;
break;
case 0x1b:
state = STATE_ESC;
break;
case 0x20 ... 0x2f:
params[params_idx++] = param;
param = 0;
ibytes = ibytes << 8 | *p;
state = STATE_CSI_INTERMEDIATE;
break;
case 0x30 ... 0x39:
param = param * 10 + *p - 0x30;
break;
case 0x3a:
ibytes = ibytes << 8;
break;
case 0x3b:
params[params_idx++] = param;
param = 0;
break;
case 0x3c ... 0x3f:
ibytes = ibytes << 8 | *p;
break;
case 0x40 ... 0x4f:
default:
state = STATE_GROUND;
break;
}
break;
case STATE_CSI_INTERMEDIATE:
switch (*p) {
case 0x00 ... 0x17:
case 0x19:
case 0x1c ... 0x1f:
case 0x7f:
break;
case 0x18:
case 0x1a:
state = STATE_GROUND;
break;
case 0x1b:
state = STATE_ESC;
break;
case 0x20 ... 0x2f:
ibytes = ibytes << 8 | *p;
break;
case 0x30 ... 0x3f:
state = STATE_GROUND;
break;
case 0x40 ... 0x7e:
ibytes = ibytes << 8 | *p;
state = STATE_GROUND;
if (ibytes == ('&' << 8 | 'w') && params_idx >= 4) {
canvas_point(&c, params[3], params[2] - offset);
if (params[1] == 4 && draw_line(&c) == 0) {
printf("\0338");
status = sixel_encode(c.buf, c.width, c.height, 0, dither, output);
if (SIXEL_FAILED(status)) {
goto end;
}
}
canvas_sync(&c);
}
break;
default:
state = STATE_GROUND;
break;
}
break;
case STATE_ESC_INTERMEDIATE:
switch (*p) {
case 0x00 ... 0x17:
case 0x19:
case 0x1c ... 0x1f:
case 0x7f:
break;
case 0x18:
case 0x1a:
state = STATE_GROUND;
break;
case 0x1b:
state = STATE_ESC;
break;
case 0x20 ... 0x2f:
state = STATE_ESC_INTERMEDIATE;
break;
case 0x30 ... 0x3f:
state = STATE_GROUND;
break;
}
break;
case STATE_OSC:
switch (*p) {
case 0x07:
case 0x18:
case 0x1a:
state = STATE_GROUND;
break;
case 0x1b:
state = STATE_ESC;
break;
default:
break;
}
break;
case STATE_STR:
switch (*p) {
case 0x18:
case 0x1a:
state = STATE_GROUND;
break;
case 0x1b:
state = STATE_ESC;
break;
default:
break;
}
break;
case STATE_SS:
switch (*p) {
case 0x1b:
state = STATE_ESC;
break;
default:
state = STATE_GROUND;
break;
}
break;
}
}
}
end:
printf("\033[?25h");
fflush(stdout);
tty_restore(&old_termios);
canvas_deinit(&c);
printf("\n");
return 0;
}