feat: kitty images patch applied
This commit is contained in:
303
st.c
303
st.c
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "st.h"
|
||||
#include "win.h"
|
||||
#include "graphics.h"
|
||||
|
||||
#if defined(__linux)
|
||||
#include <pty.h>
|
||||
@@ -36,6 +37,10 @@
|
||||
#define STR_BUF_SIZ ESC_BUF_SIZ
|
||||
#define STR_ARG_SIZ ESC_ARG_SIZ
|
||||
|
||||
/* PUA character used as an image placeholder */
|
||||
#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE
|
||||
#define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE
|
||||
|
||||
/* macros */
|
||||
#define IS_SET(flag) ((term.mode & (flag)) != 0)
|
||||
#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
|
||||
@@ -113,6 +118,8 @@ typedef struct {
|
||||
typedef struct {
|
||||
int row; /* nb row */
|
||||
int col; /* nb col */
|
||||
int pixw; /* width of the text area in pixels */
|
||||
int pixh; /* height of the text area in pixels */
|
||||
Line *line; /* screen */
|
||||
Line *alt; /* alternate screen */
|
||||
int *dirty; /* dirtyness of lines */
|
||||
@@ -213,7 +220,6 @@ static Rune utf8decodebyte(char, size_t *);
|
||||
static char utf8encodebyte(Rune, size_t);
|
||||
static size_t utf8validate(Rune *, size_t);
|
||||
|
||||
static char *base64dec(const char *);
|
||||
static char base64dec_getc(const char **);
|
||||
|
||||
static ssize_t xwrite(int, const char *, size_t);
|
||||
@@ -232,6 +238,10 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
|
||||
static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
|
||||
static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
|
||||
|
||||
/* Converts a diacritic to a row/column/etc number. The result is 1-base, 0
|
||||
* means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */
|
||||
uint16_t diacritic_to_num(uint32_t code);
|
||||
|
||||
ssize_t
|
||||
xwrite(int fd, const char *s, size_t len)
|
||||
{
|
||||
@@ -616,6 +626,12 @@ getsel(void)
|
||||
if (gp->mode & ATTR_WDUMMY)
|
||||
continue;
|
||||
|
||||
if (gp->mode & ATTR_IMAGE) {
|
||||
// TODO: Copy diacritics as well
|
||||
ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
ptr += utf8encode(gp->u, ptr);
|
||||
}
|
||||
|
||||
@@ -715,11 +731,14 @@ sigchld(int a)
|
||||
int stat;
|
||||
pid_t p;
|
||||
|
||||
if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
|
||||
if ((p = waitpid(-1, &stat, WNOHANG)) < 0)
|
||||
die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
|
||||
|
||||
if (pid != p)
|
||||
if (pid != p) {
|
||||
/* reinstall sigchld handler */
|
||||
signal(SIGCHLD, sigchld);
|
||||
return;
|
||||
}
|
||||
|
||||
if (WIFEXITED(stat) && WEXITSTATUS(stat))
|
||||
die("child exited with status %d\n", WEXITSTATUS(stat));
|
||||
@@ -819,7 +838,11 @@ ttyread(void)
|
||||
{
|
||||
static char buf[BUFSIZ];
|
||||
static int buflen = 0;
|
||||
int ret, written;
|
||||
static int already_processing = 0;
|
||||
int ret, written = 0;
|
||||
|
||||
if (buflen >= LEN(buf))
|
||||
return 0;
|
||||
|
||||
/* append read bytes to unprocessed bytes */
|
||||
ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
|
||||
@@ -831,7 +854,24 @@ ttyread(void)
|
||||
die("couldn't read from shell: %s\n", strerror(errno));
|
||||
default:
|
||||
buflen += ret;
|
||||
written = twrite(buf, buflen, 0);
|
||||
if (already_processing) {
|
||||
/* Avoid recursive call to twrite() */
|
||||
return ret;
|
||||
}
|
||||
already_processing = 1;
|
||||
while (1) {
|
||||
int buflen_before_processing = buflen;
|
||||
written += twrite(buf + written, buflen - written, 0);
|
||||
// If buflen changed during the call to twrite, there is
|
||||
// new data, and we need to keep processing, otherwise
|
||||
// we can exit. This will not loop forever because the
|
||||
// buffer is limited, and we don't clean it in this
|
||||
// loop, so at some point ttywrite will have to drop
|
||||
// some data.
|
||||
if (buflen_before_processing == buflen)
|
||||
break;
|
||||
}
|
||||
already_processing = 0;
|
||||
buflen -= written;
|
||||
/* keep any incomplete UTF-8 byte sequence for the next call */
|
||||
if (buflen > 0)
|
||||
@@ -874,6 +914,7 @@ ttywriteraw(const char *s, size_t n)
|
||||
fd_set wfd, rfd;
|
||||
ssize_t r;
|
||||
size_t lim = 256;
|
||||
int retries_left = 100;
|
||||
|
||||
/*
|
||||
* Remember that we are using a pty, which might be a modem line.
|
||||
@@ -882,6 +923,9 @@ ttywriteraw(const char *s, size_t n)
|
||||
* FIXME: Migrate the world to Plan 9.
|
||||
*/
|
||||
while (n > 0) {
|
||||
if (retries_left-- <= 0)
|
||||
goto too_many_retries;
|
||||
|
||||
FD_ZERO(&wfd);
|
||||
FD_ZERO(&rfd);
|
||||
FD_SET(cmdfd, &wfd);
|
||||
@@ -923,11 +967,16 @@ ttywriteraw(const char *s, size_t n)
|
||||
|
||||
write_error:
|
||||
die("write error on tty: %s\n", strerror(errno));
|
||||
too_many_retries:
|
||||
fprintf(stderr, "Could not write %zu bytes to tty\n", n);
|
||||
}
|
||||
|
||||
void
|
||||
ttyresize(int tw, int th)
|
||||
{
|
||||
term.pixw = tw;
|
||||
term.pixh = th;
|
||||
|
||||
struct winsize w;
|
||||
|
||||
w.ws_row = term.row;
|
||||
@@ -1018,7 +1067,8 @@ treset(void)
|
||||
term.c = (TCursor){{
|
||||
.mode = ATTR_NULL,
|
||||
.fg = defaultfg,
|
||||
.bg = defaultbg
|
||||
.bg = defaultbg,
|
||||
.decor = DECOR_DEFAULT_COLOR
|
||||
}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
|
||||
|
||||
memset(term.tabs, 0, term.col * sizeof(*term.tabs));
|
||||
@@ -1041,7 +1091,9 @@ treset(void)
|
||||
void
|
||||
tnew(int col, int row)
|
||||
{
|
||||
term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
|
||||
term = (Term){.c = {.attr = {.fg = defaultfg,
|
||||
.bg = defaultbg,
|
||||
.decor = DECOR_DEFAULT_COLOR}}};
|
||||
tresize(col, row);
|
||||
treset();
|
||||
}
|
||||
@@ -1218,9 +1270,24 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
|
||||
term.line[y][x-1].mode &= ~ATTR_WIDE;
|
||||
}
|
||||
|
||||
if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE &&
|
||||
tgetisclassicplaceholder(&term.line[y][x])) {
|
||||
// This is a workaround: don't overwrite classic placement
|
||||
// placeholders with space symbols (unlike Unicode placeholders
|
||||
// which must be overwritten by anything).
|
||||
term.line[y][x].bg = attr->bg;
|
||||
term.dirty[y] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
term.dirty[y] = 1;
|
||||
term.line[y][x] = *attr;
|
||||
term.line[y][x].u = u;
|
||||
|
||||
if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) {
|
||||
term.line[y][x].u = 0;
|
||||
term.line[y][x].mode |= ATTR_IMAGE;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1247,12 +1314,110 @@ tclearregion(int x1, int y1, int x2, int y2)
|
||||
selclear();
|
||||
gp->fg = term.c.attr.fg;
|
||||
gp->bg = term.c.attr.bg;
|
||||
gp->decor = term.c.attr.decor;
|
||||
gp->mode = 0;
|
||||
gp->u = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills a rectangle area with an image placeholder. The starting point is the
|
||||
/// cursor. Adds empty lines if needed. The placeholder will be marked as
|
||||
/// classic.
|
||||
void tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, int cols,
|
||||
int rows, char do_not_move_cursor,
|
||||
Glyph *text_underneath) {
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
int y = term.c.y;
|
||||
term.dirty[y] = 1;
|
||||
for (int col = 0; col < cols; ++col) {
|
||||
int x = term.c.x + col;
|
||||
if (x >= term.col)
|
||||
break;
|
||||
Glyph *gp = &term.line[y][x];
|
||||
if (selected(x, y))
|
||||
selclear();
|
||||
if (text_underneath) {
|
||||
Glyph *to_save = gp;
|
||||
// If there is already a classic placeholder,
|
||||
// use the text underneath it. This will leave
|
||||
// holes in images, but at least we are
|
||||
// guaranteed to restore the original text.
|
||||
if (gp->mode & ATTR_IMAGE &&
|
||||
tgetisclassicplaceholder(gp)) {
|
||||
Glyph *under =
|
||||
gr_get_glyph_underneath_image(
|
||||
tgetimgid(gp),
|
||||
tgetimgplacementid(gp),
|
||||
tgetimgcol(gp),
|
||||
tgetimgrow(gp));
|
||||
if (under)
|
||||
to_save = under;
|
||||
}
|
||||
text_underneath[cols * row + col] = *to_save;
|
||||
}
|
||||
gp->mode = ATTR_IMAGE;
|
||||
gp->u = 0;
|
||||
tsetimgrow(gp, row + 1);
|
||||
tsetimgcol(gp, col + 1);
|
||||
tsetimgid(gp, image_id);
|
||||
tsetimgplacementid(gp, placement_id);
|
||||
tsetimgdiacriticcount(gp, 3);
|
||||
tsetisclassicplaceholder(gp, 1);
|
||||
}
|
||||
// If moving the cursor is not allowed and this is the last line
|
||||
// of the terminal, we are done.
|
||||
if (do_not_move_cursor && y == term.row - 1)
|
||||
break;
|
||||
// Move the cursor down, maybe creating a new line. The x is
|
||||
// preserved (we never change term.c.x in the loop above).
|
||||
if (row != rows - 1)
|
||||
tnewline(/*first_col=*/0);
|
||||
}
|
||||
if (do_not_move_cursor) {
|
||||
// Return the cursor to the original position.
|
||||
tmoveto(term.c.x, term.c.y - rows + 1);
|
||||
} else {
|
||||
// Move the cursor beyond the last column, as required by the
|
||||
// protocol. If the cursor goes beyond the screen edge, insert a
|
||||
// newline to match the behavior of kitty.
|
||||
if (term.c.x + cols >= term.col)
|
||||
tnewline(/*first_col=*/1);
|
||||
else
|
||||
tmoveto(term.c.x + cols, term.c.y);
|
||||
}
|
||||
}
|
||||
|
||||
void gr_for_each_image_cell(int (*callback)(void *data, Glyph *gp),
|
||||
void *data) {
|
||||
for (int row = 0; row < term.row; ++row) {
|
||||
for (int col = 0; col < term.col; ++col) {
|
||||
Glyph *gp = &term.line[row][col];
|
||||
if (gp->mode & ATTR_IMAGE) {
|
||||
if (callback(data, gp))
|
||||
term.dirty[row] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gr_schedule_image_redraw_by_id(uint32_t image_id) {
|
||||
for (int row = 0; row < term.row; ++row) {
|
||||
if (term.dirty[row])
|
||||
continue;
|
||||
for (int col = 0; col < term.col; ++col) {
|
||||
Glyph *gp = &term.line[row][col];
|
||||
if (gp->mode & ATTR_IMAGE) {
|
||||
uint32_t cell_image_id = tgetimgid(gp);
|
||||
if (cell_image_id == image_id) {
|
||||
term.dirty[row] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
tdeletechar(int n)
|
||||
{
|
||||
@@ -1371,6 +1536,7 @@ tsetattr(const int *attr, int l)
|
||||
ATTR_STRUCK );
|
||||
term.c.attr.fg = defaultfg;
|
||||
term.c.attr.bg = defaultbg;
|
||||
term.c.attr.decor = DECOR_DEFAULT_COLOR;
|
||||
break;
|
||||
case 1:
|
||||
term.c.attr.mode |= ATTR_BOLD;
|
||||
@@ -1383,6 +1549,20 @@ tsetattr(const int *attr, int l)
|
||||
break;
|
||||
case 4:
|
||||
term.c.attr.mode |= ATTR_UNDERLINE;
|
||||
if (i + 1 < l) {
|
||||
idx = attr[++i];
|
||||
if (BETWEEN(idx, 1, 5)) {
|
||||
tsetdecorstyle(&term.c.attr, idx);
|
||||
} else if (idx == 0) {
|
||||
term.c.attr.mode &= ~ATTR_UNDERLINE;
|
||||
tsetdecorstyle(&term.c.attr, 0);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"erresc: unknown underline "
|
||||
"style %d\n",
|
||||
idx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 5: /* slow blink */
|
||||
/* FALLTHROUGH */
|
||||
@@ -1406,6 +1586,7 @@ tsetattr(const int *attr, int l)
|
||||
break;
|
||||
case 24:
|
||||
term.c.attr.mode &= ~ATTR_UNDERLINE;
|
||||
tsetdecorstyle(&term.c.attr, 0);
|
||||
break;
|
||||
case 25:
|
||||
term.c.attr.mode &= ~ATTR_BLINK;
|
||||
@@ -1434,10 +1615,11 @@ tsetattr(const int *attr, int l)
|
||||
term.c.attr.bg = defaultbg;
|
||||
break;
|
||||
case 58:
|
||||
/* This starts a sequence to change the color of
|
||||
* "underline" pixels. We don't support that and
|
||||
* instead eat up a following "5;n" or "2;r;g;b". */
|
||||
tdefcolor(attr, &i, l);
|
||||
if ((idx = tdefcolor(attr, &i, l)) >= 0)
|
||||
tsetdecorcolor(&term.c.attr, idx);
|
||||
break;
|
||||
case 59:
|
||||
tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR);
|
||||
break;
|
||||
default:
|
||||
if (BETWEEN(attr[i], 30, 37)) {
|
||||
@@ -1826,6 +2008,39 @@ csihandle(void)
|
||||
goto unknown;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
switch (csiescseq.mode[1]) {
|
||||
case 'q': /* XTVERSION -- Print terminal name and version */
|
||||
len = snprintf(buf, sizeof(buf),
|
||||
"\033P>|st-graphics(%s)\033\\", VERSION);
|
||||
ttywrite(buf, len, 0);
|
||||
break;
|
||||
default:
|
||||
goto unknown;
|
||||
}
|
||||
break;
|
||||
case 't': /* XTWINOPS -- Window manipulation */
|
||||
switch (csiescseq.arg[0]) {
|
||||
case 14: /* Report text area size in pixels. */
|
||||
len = snprintf(buf, sizeof(buf), "\033[4;%i;%it",
|
||||
term.pixh, term.pixw);
|
||||
ttywrite(buf, len, 0);
|
||||
break;
|
||||
case 16: /* Report character cell size in pixels. */
|
||||
len = snprintf(buf, sizeof(buf), "\033[6;%i;%it",
|
||||
term.pixh / term.row,
|
||||
term.pixw / term.col);
|
||||
ttywrite(buf, len, 0);
|
||||
break;
|
||||
case 18: /* Report the size of the text area in characters. */
|
||||
len = snprintf(buf, sizeof(buf), "\033[8;%i;%it",
|
||||
term.row, term.col);
|
||||
ttywrite(buf, len, 0);
|
||||
break;
|
||||
default:
|
||||
goto unknown;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1988,8 +2203,27 @@ strhandle(void)
|
||||
case 'k': /* old title set compatibility */
|
||||
xsettitle(strescseq.args[0]);
|
||||
return;
|
||||
case 'P': /* DCS -- Device Control String */
|
||||
case '_': /* APC -- Application Program Command */
|
||||
if (gr_parse_command(strescseq.buf, strescseq.len)) {
|
||||
GraphicsCommandResult *res = &graphics_command_result;
|
||||
if (res->create_placeholder) {
|
||||
tcreateimgplaceholder(
|
||||
res->placeholder.image_id,
|
||||
res->placeholder.placement_id,
|
||||
res->placeholder.columns,
|
||||
res->placeholder.rows,
|
||||
res->placeholder.do_not_move_cursor,
|
||||
res->placeholder.text_underneath);
|
||||
}
|
||||
if (res->response[0])
|
||||
ttywrite(res->response, strlen(res->response),
|
||||
0);
|
||||
if (res->redraw)
|
||||
tfulldirt();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
case 'P': /* DCS -- Device Control String */
|
||||
case '^': /* PM -- Privacy Message */
|
||||
return;
|
||||
}
|
||||
@@ -2496,6 +2730,41 @@ check_control_code:
|
||||
if (selected(term.c.x, term.c.y))
|
||||
selclear();
|
||||
|
||||
// wcwidth is broken on some systems, set the width to 0 if it's a known
|
||||
// diacritic used for images.
|
||||
uint16_t num = diacritic_to_num(u);
|
||||
if (num != 0)
|
||||
width = 0;
|
||||
// Set the width to 1 if it's an image placeholder character.
|
||||
if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD)
|
||||
width = 1;
|
||||
|
||||
if (width == 0) {
|
||||
// It's probably a combining char. Combining characters are not
|
||||
// supported, so we just ignore them, unless it denotes the row and
|
||||
// column of an image character.
|
||||
if (term.c.y <= 0 && term.c.x <= 0)
|
||||
return;
|
||||
else if (term.c.x == 0)
|
||||
gp = &term.line[term.c.y-1][term.col-1];
|
||||
else if (term.c.state & CURSOR_WRAPNEXT)
|
||||
gp = &term.line[term.c.y][term.c.x];
|
||||
else
|
||||
gp = &term.line[term.c.y][term.c.x-1];
|
||||
if (num && (gp->mode & ATTR_IMAGE)) {
|
||||
unsigned diaccount = tgetimgdiacriticcount(gp);
|
||||
if (diaccount == 0)
|
||||
tsetimgrow(gp, num);
|
||||
else if (diaccount == 1)
|
||||
tsetimgcol(gp, num);
|
||||
else if (diaccount == 2)
|
||||
tsetimg4thbyteplus1(gp, num);
|
||||
tsetimgdiacriticcount(gp, diaccount + 1);
|
||||
}
|
||||
term.lastc = u;
|
||||
return;
|
||||
}
|
||||
|
||||
gp = &term.line[term.c.y][term.c.x];
|
||||
if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
|
||||
gp->mode |= ATTR_WRAP;
|
||||
@@ -2662,6 +2931,8 @@ drawregion(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
int y;
|
||||
|
||||
xstartimagedraw(term.dirty, term.row);
|
||||
|
||||
for (y = y1; y < y2; y++) {
|
||||
if (!term.dirty[y])
|
||||
continue;
|
||||
@@ -2669,6 +2940,8 @@ drawregion(int x1, int y1, int x2, int y2)
|
||||
term.dirty[y] = 0;
|
||||
xdrawline(term.line[y], x1, y, x2);
|
||||
}
|
||||
|
||||
xfinishimagedraw();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2703,3 +2976,9 @@ redraw(void)
|
||||
tfulldirt();
|
||||
draw();
|
||||
}
|
||||
|
||||
Glyph
|
||||
getglyphat(int col, int row)
|
||||
{
|
||||
return term.line[row][col];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user