From 8e5f25f43a0367976630e7a7c93e09ffbcb6ea65 Mon Sep 17 00:00:00 2001 From: mintycube <90507714+mintycube@users.noreply.github.com> Date: Tue, 21 May 2024 22:01:45 +0500 Subject: [PATCH] Add sixel to st --- .config/suckless/st/README.md | 1 + .config/suckless/st/config.h | 13 +- .config/suckless/st/config.mk | 4 +- .config/suckless/st/patch/reflow.c | 106 +++++ .config/suckless/st/sixel.c | 687 +++++++++++++++++++++++++++++ .config/suckless/st/sixel.h | 63 +++ .config/suckless/st/sixel_hls.c | 115 +++++ .config/suckless/st/sixel_hls.h | 7 + .config/suckless/st/st.c | 213 ++++++++- .config/suckless/st/st.h | 19 + .config/suckless/st/x.c | 121 ++++- 11 files changed, 1337 insertions(+), 12 deletions(-) create mode 100644 .config/suckless/st/sixel.c create mode 100644 .config/suckless/st/sixel.h create mode 100644 .config/suckless/st/sixel_hls.c create mode 100644 .config/suckless/st/sixel_hls.h diff --git a/.config/suckless/st/README.md b/.config/suckless/st/README.md index 4946c01..1de10e6 100644 --- a/.config/suckless/st/README.md +++ b/.config/suckless/st/README.md @@ -23,6 +23,7 @@ The patches used are listed below: - rightclicktoplumb [link](https://st.suckless.org/patches/right_click_to_plumb) - scrollback [link](https://st.suckless.org/patches/scrollback) - scrollback mouse altscreen [link](https://st.suckless.org/patches/scrollback) +- sixel [link](https://gist.github.com/saitoha/70e0fdf22e3e8f63ce937c7f7da71809) - swapmouse [link](https://st.suckless.org/patches/swapmouse) - use xftfontmatch [link](https://git.suckless.org/st/commit/528241aa3835e2f1f052abeeaf891737712955a0.html) - wide glyphs [link](https://github.com/Dreomite/st/commit/e3b821dcb3511d60341dec35ee05a4a0abfef7f2) [link](https://www.reddit.com/r/suckless/comments/jt90ai/update_support_for_proper_glyph_rendering_in_st) diff --git a/.config/suckless/st/config.h b/.config/suckless/st/config.h index 8772e90..c425e8d 100644 --- a/.config/suckless/st/config.h +++ b/.config/suckless/st/config.h @@ -30,7 +30,10 @@ char *scroll = NULL; char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; /* identification sequence returned in DA and DECID */ -char *vtiden = "\033[?6c"; +char *vtiden = "\033[?62;4c"; /* VT200 family (62) with sixel (4) */ + +/* sixel rgb byte order: LSBFirst or MSBFirst */ +int const sixelbyteorder = LSBFirst; /* Kerning / character bounding-box multipliers */ static float cwscale = 1.0; @@ -158,7 +161,7 @@ unsigned int defaultrcs = 257; * 7: Blinking st cursor * 8: Steady st cursor */ -static unsigned int cursorstyle = 1; +static unsigned int cursorstyle = 3; static Rune stcursor = 0x2603; /* snowman (U+2603) */ /* @@ -266,10 +269,8 @@ static Shortcut shortcuts[] = { {ControlMask, XK_Print, toggleprinter, {.i = 0}}, {ShiftMask, XK_Print, printscreen, {.i = 0}}, {XK_ANY_MOD, XK_Print, printsel, {.i = 0}}, - {TERMMOD, XK_Up, zoom, {.f = +2}}, - {TERMMOD, XK_Down, zoom, {.f = -2}}, - // {MODKEY, XK_equal, zoom, {.f = +2}}, - // {MODKEY, XK_minus, zoom, {.f = -2}}, + {TERMMOD, XK_Up, zoom, {.f = +1}}, + {TERMMOD, XK_Down, zoom, {.f = -1}}, {TERMMOD, XK_Home, zoomreset, {.f = 0}}, {TERMMOD, XK_C, clipcopy, {.i = 0}}, {TERMMOD, XK_V, clippaste, {.i = 0}}, diff --git a/.config/suckless/st/config.mk b/.config/suckless/st/config.mk index a8bce03..31af57d 100644 --- a/.config/suckless/st/config.mk +++ b/.config/suckless/st/config.mk @@ -27,8 +27,8 @@ LIGATURES_INC = `$(PKG_CONFIG) --cflags harfbuzz` LIGATURES_LIBS = `$(PKG_CONFIG) --libs harfbuzz` # Uncomment this for the SIXEL patch / SIXEL_PATCH -#SIXEL_C = sixel.c sixel_hls.c -#SIXEL_LIBS = `$(PKG_CONFIG) --libs imlib2` +SIXEL_C = sixel.c sixel_hls.c +SIXEL_LIBS = `$(PKG_CONFIG) --libs imlib2` # Uncomment for the netwmicon patch / NETWMICON_PATCH #NETWMICON_LIBS = `$(PKG_CONFIG) --libs gdlib` diff --git a/.config/suckless/st/patch/reflow.c b/.config/suckless/st/patch/reflow.c index 53cc698..f0bd780 100644 --- a/.config/suckless/st/patch/reflow.c +++ b/.config/suckless/st/patch/reflow.c @@ -6,6 +6,7 @@ tloaddefscreen(int clear, int loadcursor) if (alt) { if (clear) { tclearregion(0, 0, term.col-1, term.row-1, 1); + tdeleteimages(); } col = term.col, row = term.row; tswapscreen(); @@ -31,6 +32,7 @@ tloadaltscreen(int clear, int savecursor) } if (clear) { tclearregion(0, 0, term.col-1, term.row-1, 1); + tdeleteimages(); } } @@ -55,6 +57,17 @@ tclearglyph(Glyph *gp, int usecurattr) gp->u = ' '; } +void +treflow_moveimages(int oldy, int newy) +{ + ImageList *im; + + for (im = term.images; im; im = im->next) { + if (im->y == oldy) + im->reflow_y = newy; + } +} + void treflow(int col, int row) { @@ -64,6 +77,10 @@ treflow(int col, int row) int cy = -1; /* proxy for new y coordinate of cursor */ int buflen, nlines; Line *buf, bufline, line; + ImageList *im, *next; + + for (im = term.images; im; im = im->next) + im->reflow_y = INT_MIN; /* unset reflow_y */ /* y coordinate of cursor line end */ for (oce = term.c.y; oce < term.row - 1 && @@ -95,6 +112,7 @@ treflow(int col, int row) if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { for (j = nx; j < col; j++) tclearglyph(&bufline[j], 0); + treflow_moveimages(oy+term.scr, ny); nx = 0; } else if (nx > 0) { bufline[nx - 1].mode &= ~ATTR_WRAP; @@ -102,6 +120,7 @@ treflow(int col, int row) ox = 0, oy++; } else if (col - nx == len - ox) { memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph)); + treflow_moveimages(oy+term.scr, ny); ox = 0, oy++, nx = 0; } else/* if (col - nx < len - ox) */ { memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph)); @@ -112,6 +131,7 @@ treflow(int col, int row) } else { bufline[col - 1].mode |= ATTR_WRAP; } + treflow_moveimages(oy+term.scr, ny); ox += col - nx; nx = 0; } @@ -172,6 +192,29 @@ treflow(int col, int row) term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); } + /* move images to the final position */ + for (im = term.images; im; im = next) { + next = im->next; + if (im->reflow_y == INT_MIN) { + delete_image(im); + } else { + im->y = im->reflow_y - term.histf + term.scr - (ny + 1); + if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= row) + delete_image(im); + } + } + + /* expand images into new text cells */ + for (im = term.images; im; im = next) { + next = im->next; + if (im->x < col) { + line = TLINE(im->y); + x2 = MIN(im->x + im->cols, col); + for (x = im->x; x < x2; x++) + line[x].mode |= ATTR_SIXEL; + } + } + for (; buflen > 0; ny--, buflen--) free(buf[ny % nlines]); free(buf); @@ -206,6 +249,7 @@ rscrolldown(int n) if ((i = term.scr - n) >= 0) { term.scr = i; } else { + scroll_images(n - term.scr); term.scr = 0; if (sel.ob.x != -1 && !sel.alt) selmove(-i); @@ -258,6 +302,7 @@ void tresizealt(int col, int row) { int i, j; + ImageList *im, *next; /* return if dimensions haven't changed */ if (term.col == col && term.row == row) { @@ -272,6 +317,7 @@ tresizealt(int col, int row) if (i > 0) { /* ensure that both src and dst are not NULL */ memmove(term.line, term.line + i, row * sizeof(Line)); + scroll_images(-i); term.c.y = row - 1; } for (i += row; i < term.row; i++) @@ -302,6 +348,17 @@ tresizealt(int col, int row) /* reset scrolling region */ term.top = 0, term.bot = row - 1; + /* delete or clip images if they are not inside the screen */ + for (im = term.images; im; im = next) { + next = im->next; + if (im->x >= term.col || im->y >= term.row || im->y < 0) { + delete_image(im); + } else { + if ((im->cols = MIN(im->x + im->cols, term.col) - im->x) <= 0) + delete_image(im); + } + } + /* dirty all lines */ tfulldirt(); } @@ -328,6 +385,8 @@ kscrolldown(const Arg* a) selmove(-n); /* negate change in term.scr */ tfulldirt(); + scroll_images(-1*n); + } void @@ -352,6 +411,8 @@ kscrollup(const Arg* a) selmove(n); /* negate change in term.scr */ tfulldirt(); + scroll_images(n); + } void @@ -363,6 +424,8 @@ tscrollup(int top, int bot, int n, int mode) int alt = IS_SET(MODE_ALTSCREEN); int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; int scr = alt ? 0 : term.scr; + int itop = top + scr, ibot = bot + scr; + ImageList *im, *next; if (n <= 0) return; @@ -397,6 +460,35 @@ tscrollup(int top, int bot, int n, int mode) term.line[i+n] = temp; } + if (alt || !savehist) { + /* move images, if they are inside the scrolling region */ + for (im = term.images; im; im = next) { + next = im->next; + if (im->y >= itop && im->y <= ibot) { + im->y -= n; + if (im->y < itop) + delete_image(im); + } + } + } else { + /* move images, if they are inside the scrolling region or scrollback */ + for (im = term.images; im; im = next) { + next = im->next; + im->y -= scr; + if (im->y < 0) { + im->y -= n; + } else if (im->y >= top && im->y <= bot) { + im->y -= n; + if (im->y < top) + im->y -= top; // move to scrollback + } + if (im->y < -HISTSIZE) + delete_image(im); + else + im->y += term.scr; + } + } + if (sel.ob.x != -1 && sel.alt == alt) { if (!savehist) { selscroll(top, bot, -n); @@ -416,6 +508,7 @@ tscrolldown(int top, int n) int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; int itop = top + scr, ibot = bot + scr; Line temp; + ImageList *im, *next; if (n <= 0) return; @@ -430,6 +523,16 @@ tscrolldown(int top, int n) term.line[i-n] = temp; } + /* move images, if they are inside the scrolling region */ + for (im = term.images; im; im = next) { + next = im->next; + if (im->y >= itop && im->y <= ibot) { + im->y += n; + if (im->y > ibot) + delete_image(im); + } + } + if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) selscroll(top, bot, n); } @@ -692,6 +795,7 @@ tswapscreen(void) static int altcol, altrow; Line *tmpline = term.line; int tmpcol = term.col, tmprow = term.row; + ImageList *im = term.images; term.line = altline; term.col = altcol, term.row = altrow; @@ -699,6 +803,8 @@ tswapscreen(void) altcol = tmpcol, altrow = tmprow; term.mode ^= MODE_ALTSCREEN; + term.images = term.images_alt; + term.images_alt = im; } char * diff --git a/.config/suckless/st/sixel.c b/.config/suckless/st/sixel.c new file mode 100644 index 0000000..236d5ec --- /dev/null +++ b/.config/suckless/st/sixel.c @@ -0,0 +1,687 @@ +// sixel.c (part of mintty) +// originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c) +// Licensed under the terms of the GNU General Public License v3 or later. + +#include +#include /* memcpy */ + +#include "st.h" +#include "win.h" +#include "sixel.h" +#include "sixel_hls.h" + +#define SIXEL_RGB(r, g, b) ((255 << 24) + ((r) << 16) + ((g) << 8) + (b)) +#define SIXEL_PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m)) +#define SIXEL_XRGB(r,g,b) SIXEL_RGB(SIXEL_PALVAL(r, 255, 100), SIXEL_PALVAL(g, 255, 100), SIXEL_PALVAL(b, 255, 100)) + +static sixel_color_t const sixel_default_color_table[] = { + SIXEL_XRGB( 0, 0, 0), /* 0 Black */ + SIXEL_XRGB(20, 20, 80), /* 1 Blue */ + SIXEL_XRGB(80, 13, 13), /* 2 Red */ + SIXEL_XRGB(20, 80, 20), /* 3 Green */ + SIXEL_XRGB(80, 20, 80), /* 4 Magenta */ + SIXEL_XRGB(20, 80, 80), /* 5 Cyan */ + SIXEL_XRGB(80, 80, 20), /* 6 Yellow */ + SIXEL_XRGB(53, 53, 53), /* 7 Gray 50% */ + SIXEL_XRGB(26, 26, 26), /* 8 Gray 25% */ + SIXEL_XRGB(33, 33, 60), /* 9 Blue* */ + SIXEL_XRGB(60, 26, 26), /* 10 Red* */ + SIXEL_XRGB(33, 60, 33), /* 11 Green* */ + SIXEL_XRGB(60, 33, 60), /* 12 Magenta* */ + SIXEL_XRGB(33, 60, 60), /* 13 Cyan* */ + SIXEL_XRGB(60, 60, 33), /* 14 Yellow* */ + SIXEL_XRGB(80, 80, 80), /* 15 Gray 75% */ +}; + +void +scroll_images(int n) { + ImageList *im, *next; + int top = tisaltscr() ? 0 : term.scr - HISTSIZE; + + for (im = term.images; im; im = next) { + next = im->next; + im->y += n; + + /* check if the current sixel has exceeded the maximum + * draw distance, and should therefore be deleted */ + if (im->y < top) { + //fprintf(stderr, "im@0x%08x exceeded maximum distance\n"); + delete_image(im); + } + } +} + +void +delete_image(ImageList *im) +{ + if (im->prev) + im->prev->next = im->next; + else + term.images = im->next; + if (im->next) + im->next->prev = im->prev; + if (im->pixmap) + XFreePixmap(xw.dpy, (Drawable)im->pixmap); + if (im->clipmask) + XFreePixmap(xw.dpy, (Drawable)im->clipmask); + free(im->pixels); + free(im); +} + +static int +set_default_color(sixel_image_t *image) +{ + int i; + int n; + int r; + int g; + int b; + + /* palette initialization */ + for (n = 1; n < 17; n++) { + image->palette[n] = sixel_default_color_table[n - 1]; + } + + /* colors 17-232 are a 6x6x6 color cube */ + for (r = 0; r < 6; r++) { + for (g = 0; g < 6; g++) { + for (b = 0; b < 6; b++) { + image->palette[n++] = SIXEL_RGB(r * 51, g * 51, b * 51); + } + } + } + + /* colors 233-256 are a grayscale ramp, intentionally leaving out */ + for (i = 0; i < 24; i++) { + image->palette[n++] = SIXEL_RGB(i * 11, i * 11, i * 11); + } + + for (; n < DECSIXEL_PALETTE_MAX; n++) { + image->palette[n] = SIXEL_RGB(255, 255, 255); + } + + return (0); +} + +static int +sixel_image_init( + sixel_image_t *image, + int width, + int height, + int fgcolor, + int bgcolor, + int use_private_register) +{ + int status = (-1); + size_t size; + + size = (size_t)(width * height) * sizeof(sixel_color_no_t); + image->width = width; + image->height = height; + image->data = (sixel_color_no_t *)malloc(size); + image->ncolors = 2; + image->use_private_register = use_private_register; + + if (image->data == NULL) { + status = (-1); + goto end; + } + memset(image->data, 0, size); + + image->palette[0] = bgcolor; + + if (image->use_private_register) + image->palette[1] = fgcolor; + + image->palette_modified = 0; + + status = (0); + +end: + return status; +} + +static int +image_buffer_resize( + sixel_image_t *image, + int width, + int height) +{ + int status = (-1); + size_t size; + sixel_color_no_t *alt_buffer; + int n; + int min_height; + + size = (size_t)(width * height) * sizeof(sixel_color_no_t); + alt_buffer = (sixel_color_no_t *)malloc(size); + if (alt_buffer == NULL) { + /* free source image */ + free(image->data); + image->data = NULL; + status = (-1); + goto end; + } + + min_height = height > image->height ? image->height: height; + if (width > image->width) { /* if width is extended */ + for (n = 0; n < min_height; ++n) { + /* copy from source image */ + memcpy(alt_buffer + width * n, + image->data + image->width * n, + (size_t)image->width * sizeof(sixel_color_no_t)); + /* fill extended area with background color */ + memset(alt_buffer + width * n + image->width, + 0, + (size_t)(width - image->width) * sizeof(sixel_color_no_t)); + } + } else { + for (n = 0; n < min_height; ++n) { + /* copy from source image */ + memcpy(alt_buffer + width * n, + image->data + image->width * n, + (size_t)width * sizeof(sixel_color_no_t)); + } + } + + if (height > image->height) { /* if height is extended */ + /* fill extended area with background color */ + memset(alt_buffer + width * image->height, + 0, + (size_t)(width * (height - image->height)) * sizeof(sixel_color_no_t)); + } + + /* free source image */ + free(image->data); + + image->data = alt_buffer; + image->width = width; + image->height = height; + + status = (0); + +end: + return status; +} + +static void +sixel_image_deinit(sixel_image_t *image) +{ + if (image->data) + free(image->data); + image->data = NULL; +} + +int +sixel_parser_init(sixel_state_t *st, + int transparent, + sixel_color_t fgcolor, sixel_color_t bgcolor, + unsigned char use_private_register, + int cell_width, int cell_height) +{ + int status = (-1); + + st->state = PS_DECSIXEL; + st->pos_x = 0; + st->pos_y = 0; + st->max_x = 0; + st->max_y = 0; + st->attributed_pan = 2; + st->attributed_pad = 1; + st->attributed_ph = 0; + st->attributed_pv = 0; + st->transparent = transparent; + st->repeat_count = 1; + st->color_index = 16; + st->grid_width = cell_width; + st->grid_height = cell_height; + st->nparams = 0; + st->param = 0; + + /* buffer initialization */ + status = sixel_image_init(&st->image, 1, 1, fgcolor, transparent ? 0 : bgcolor, use_private_register); + + return status; +} + +int +sixel_parser_set_default_color(sixel_state_t *st) +{ + return set_default_color(&st->image); +} + +int +sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, int cw, int ch) +{ + sixel_image_t *image = &st->image; + int x, y; + sixel_color_no_t *src; + sixel_color_t *dst; + int color; + int w, h; + int i, j, cols, numimages; + ImageList *im, *next, *tail; + + if (!image->data) + return -1; + + if (++st->max_x < st->attributed_ph) + st->max_x = st->attributed_ph; + + if (++st->max_y < st->attributed_pv) + st->max_y = st->attributed_pv; + + if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) { + if (set_default_color(image) < 0) + return -1; + } + + w = MIN(st->max_x, image->width); + h = MIN(st->max_y, image->height); + + if ((numimages = (h + ch-1) / ch) <= 0) + return -1; + + cols = (w + cw-1) / cw; + + *newimages = NULL, tail = NULL; + for (y = 0, i = 0; i < numimages; i++) { + if ((im = malloc(sizeof(ImageList)))) { + if (!tail) { + *newimages = tail = im; + im->prev = im->next = NULL; + } else { + tail->next = im; + im->prev = tail; + im->next = NULL; + tail = im; + } + im->x = cx; + im->y = cy + i; + im->cols = cols; + im->width = w; + im->height = MIN(h - ch * i, ch); + im->pixels = malloc(im->width * im->height * 4); + im->pixmap = NULL; + im->clipmask = NULL; + im->cw = cw; + im->ch = ch; + im->transparent = st->transparent; + } + if (!im || !im->pixels) { + for (im = *newimages; im; im = next) { + next = im->next; + if (im->pixels) + free(im->pixels); + free(im); + } + *newimages = NULL; + return -1; + } + dst = (sixel_color_t *)im->pixels; + for (j = 0; j < im->height && y < h; j++, y++) { + src = st->image.data + image->width * y; + for (x = 0; x < w; x++) + *dst++ = st->image.palette[*src++]; + } + } + + return numimages; +} + +/* convert sixel data into indexed pixel bytes and palette data */ +int +sixel_parser_parse(sixel_state_t *st, const unsigned char *p, size_t len) +{ + int n = 0; + int i; + int x; + int y; + int bits; + int sx; + int sy; + int c; + int pos; + int width; + const unsigned char *p0 = p, *p2 = p + len; + sixel_image_t *image = &st->image; + sixel_color_no_t *data, color_index; + + if (!image->data) + st->state = PS_ERROR; + + while (p < p2) { + switch (st->state) { + case PS_ESC: + goto end; + + case PS_DECSIXEL: + switch (*p) { + case '\x1b': + st->state = PS_ESC; + break; + case '"': + st->param = 0; + st->nparams = 0; + st->state = PS_DECGRA; + p++; + break; + case '!': + st->param = 0; + st->nparams = 0; + st->state = PS_DECGRI; + p++; + break; + case '#': + st->param = 0; + st->nparams = 0; + st->state = PS_DECGCI; + p++; + break; + case '$': + /* DECGCR Graphics Carriage Return */ + st->pos_x = 0; + p++; + break; + case '-': + /* DECGNL Graphics Next Line */ + st->pos_x = 0; + if (st->pos_y < DECSIXEL_HEIGHT_MAX - 5 - 6) + st->pos_y += 6; + else + st->pos_y = DECSIXEL_HEIGHT_MAX + 1; + p++; + break; + default: + if (*p >= '?' && *p <= '~') { /* sixel characters */ + if ((image->width < (st->pos_x + st->repeat_count) || image->height < (st->pos_y + 6)) + && image->width < DECSIXEL_WIDTH_MAX && image->height < DECSIXEL_HEIGHT_MAX) { + sx = image->width * 2; + sy = image->height * 2; + while (sx < (st->pos_x + st->repeat_count) || sy < (st->pos_y + 6)) { + sx *= 2; + sy *= 2; + } + + sx = MIN(sx, DECSIXEL_WIDTH_MAX); + sy = MIN(sy, DECSIXEL_HEIGHT_MAX); + + if (image_buffer_resize(image, sx, sy) < 0) { + perror("sixel_parser_parse() failed"); + st->state = PS_ERROR; + p++; + break; + } + } + + if (st->color_index > image->ncolors) + image->ncolors = st->color_index; + + if (st->pos_x + st->repeat_count > image->width) + st->repeat_count = image->width - st->pos_x; + + if (st->repeat_count > 0 && st->pos_y + 5 < image->height) { + bits = *p - '?'; + if (bits != 0) { + data = image->data + image->width * st->pos_y + st->pos_x; + width = image->width; + color_index = st->color_index; + if (st->repeat_count <= 1) { + if (bits & 0x01) + *data = color_index, n = 0; + data += width; + if (bits & 0x02) + *data = color_index, n = 1; + data += width; + if (bits & 0x04) + *data = color_index, n = 2; + data += width; + if (bits & 0x08) + *data = color_index, n = 3; + data += width; + if (bits & 0x10) + *data = color_index, n = 4; + if (bits & 0x20) + data[width] = color_index, n = 5; + if (st->max_x < st->pos_x) + st->max_x = st->pos_x; + } else { + /* st->repeat_count > 1 */ + for (i = 0; bits; bits >>= 1, i++, data += width) { + if (bits & 1) { + data[0] = color_index; + data[1] = color_index; + for (x = 2; x < st->repeat_count; x++) + data[x] = color_index; + n = i; + } + } + if (st->max_x < (st->pos_x + st->repeat_count - 1)) + st->max_x = st->pos_x + st->repeat_count - 1; + } + if (st->max_y < (st->pos_y + n)) + st->max_y = st->pos_y + n; + } + } + if (st->repeat_count > 0) + st->pos_x += st->repeat_count; + st->repeat_count = 1; + } + p++; + break; + } + break; + + case PS_DECGRA: + /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ + switch (*p) { + case '\x1b': + st->state = PS_ESC; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + st->param = st->param * 10 + *p - '0'; + st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX); + p++; + break; + case ';': + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + st->param = 0; + p++; + break; + default: + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + if (st->nparams > 0) + st->attributed_pad = st->params[0]; + if (st->nparams > 1) + st->attributed_pan = st->params[1]; + if (st->nparams > 2 && st->params[2] > 0) + st->attributed_ph = st->params[2]; + if (st->nparams > 3 && st->params[3] > 0) + st->attributed_pv = st->params[3]; + + if (st->attributed_pan <= 0) + st->attributed_pan = 1; + if (st->attributed_pad <= 0) + st->attributed_pad = 1; + + if (image->width < st->attributed_ph || + image->height < st->attributed_pv) { + sx = MAX(image->width, st->attributed_ph); + sy = MAX(image->height, st->attributed_pv); + + /* the height of the image buffer must be divisible by 6 + * to avoid unnecessary resizing of the image buffer when + * parsing the last sixel line */ + sy = (sy + 5) / 6 * 6; + + sx = MIN(sx, DECSIXEL_WIDTH_MAX); + sy = MIN(sy, DECSIXEL_HEIGHT_MAX); + + if (image_buffer_resize(image, sx, sy) < 0) { + perror("sixel_parser_parse() failed"); + st->state = PS_ERROR; + break; + } + } + st->state = PS_DECSIXEL; + st->param = 0; + st->nparams = 0; + } + break; + + case PS_DECGRI: + /* DECGRI Graphics Repeat Introducer ! Pn Ch */ + switch (*p) { + case '\x1b': + st->state = PS_ESC; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + st->param = st->param * 10 + *p - '0'; + st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX); + p++; + break; + default: + st->repeat_count = MAX(st->param, 1); + st->state = PS_DECSIXEL; + st->param = 0; + st->nparams = 0; + break; + } + break; + + case PS_DECGCI: + /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ + switch (*p) { + case '\x1b': + st->state = PS_ESC; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + st->param = st->param * 10 + *p - '0'; + st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX); + p++; + break; + case ';': + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + st->param = 0; + p++; + break; + default: + st->state = PS_DECSIXEL; + if (st->nparams < DECSIXEL_PARAMS_MAX) + st->params[st->nparams++] = st->param; + st->param = 0; + + if (st->nparams > 0) { + st->color_index = 1 + st->params[0]; /* offset 1(background color) added */ + if (st->color_index < 0) + st->color_index = 0; + else if (st->color_index >= DECSIXEL_PALETTE_MAX) + st->color_index = DECSIXEL_PALETTE_MAX - 1; + } + + if (st->nparams > 4) { + st->image.palette_modified = 1; + if (st->params[1] == 1) { + /* HLS */ + st->params[2] = MIN(st->params[2], 360); + st->params[3] = MIN(st->params[3], 100); + st->params[4] = MIN(st->params[4], 100); + image->palette[st->color_index] + = hls_to_rgb(st->params[2], st->params[3], st->params[4]); + } else if (st->params[1] == 2) { + /* RGB */ + st->params[2] = MIN(st->params[2], 100); + st->params[3] = MIN(st->params[3], 100); + st->params[4] = MIN(st->params[4], 100); + image->palette[st->color_index] + = SIXEL_XRGB(st->params[2], st->params[3], st->params[4]); + } + } + break; + } + break; + + case PS_ERROR: + if (*p == '\x1b') { + st->state = PS_ESC; + goto end; + } + p++; + break; + default: + break; + } + } + +end: + return p - p0; +} + +void +sixel_parser_deinit(sixel_state_t *st) +{ + if (st) + sixel_image_deinit(&st->image); +} + +Pixmap +sixel_create_clipmask(char *pixels, int width, int height) +{ + char c, *clipdata, *dst; + int b, i, n, y, w; + int msb = (XBitmapBitOrder(xw.dpy) == MSBFirst); + sixel_color_t *src = (sixel_color_t *)pixels; + Pixmap clipmask; + + clipdata = dst = malloc((width+7)/8 * height); + if (!clipdata) + return (Pixmap)None; + + for (y = 0; y < height; y++) { + for (w = width; w > 0; w -= n) { + n = MIN(w, 8); + if (msb) { + for (b = 0x80, c = 0, i = 0; i < n; i++, b >>= 1) + c |= (*src++) ? b : 0; + } else { + for (b = 0x01, c = 0, i = 0; i < n; i++, b <<= 1) + c |= (*src++) ? b : 0; + } + *dst++ = c; + } + } + + clipmask = XCreateBitmapFromData(xw.dpy, xw.win, clipdata, width, height); + free(clipdata); + return clipmask; +} diff --git a/.config/suckless/st/sixel.h b/.config/suckless/st/sixel.h new file mode 100644 index 0000000..7d14f8a --- /dev/null +++ b/.config/suckless/st/sixel.h @@ -0,0 +1,63 @@ +#ifndef SIXEL_H +#define SIXEL_H + +#define DECSIXEL_PARAMS_MAX 16 +#define DECSIXEL_PALETTE_MAX 1024 +#define DECSIXEL_PARAMVALUE_MAX 65535 +#define DECSIXEL_WIDTH_MAX 4096 +#define DECSIXEL_HEIGHT_MAX 4096 + +typedef unsigned short sixel_color_no_t; +typedef unsigned int sixel_color_t; + +typedef struct sixel_image_buffer { + sixel_color_no_t *data; + int width; + int height; + sixel_color_t palette[DECSIXEL_PALETTE_MAX]; + sixel_color_no_t ncolors; + int palette_modified; + int use_private_register; +} sixel_image_t; + +typedef enum parse_state { + PS_ESC = 1, /* ESC */ + PS_DECSIXEL = 2, /* DECSIXEL body part ", $, -, ? ... ~ */ + PS_DECGRA = 3, /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ + PS_DECGRI = 4, /* DECGRI Graphics Repeat Introducer ! Pn Ch */ + PS_DECGCI = 5, /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ + PS_ERROR = 6, +} parse_state_t; + +typedef struct parser_context { + parse_state_t state; + int pos_x; + int pos_y; + int max_x; + int max_y; + int attributed_pan; + int attributed_pad; + int attributed_ph; + int attributed_pv; + int transparent; + int repeat_count; + int color_index; + int bgindex; + int grid_width; + int grid_height; + int param; + int nparams; + int params[DECSIXEL_PARAMS_MAX]; + sixel_image_t image; +} sixel_state_t; + +void scroll_images(int n); +void delete_image(ImageList *im); +int sixel_parser_init(sixel_state_t *st, int transparent, sixel_color_t fgcolor, sixel_color_t bgcolor, unsigned char use_private_register, int cell_width, int cell_height); +int sixel_parser_parse(sixel_state_t *st, const unsigned char *p, size_t len); +int sixel_parser_set_default_color(sixel_state_t *st); +int sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, int cw, int ch); +void sixel_parser_deinit(sixel_state_t *st); +Pixmap sixel_create_clipmask(char *pixels, int width, int height); + +#endif diff --git a/.config/suckless/st/sixel_hls.c b/.config/suckless/st/sixel_hls.c new file mode 100644 index 0000000..c88241c --- /dev/null +++ b/.config/suckless/st/sixel_hls.c @@ -0,0 +1,115 @@ +// sixel.c (part of mintty) +// this function is derived from a part of graphics.c +// in Xterm pl#310 originally written by Ross Combs. +// +// Copyright 2013,2014 by Ross Combs +// +// All Rights Reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name(s) of the above copyright +// holders shall not be used in advertising or otherwise to promote the +// sale, use or other dealings in this Software without prior written +// authorization. + +#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) + ((b) << 16) + (255 << 24)) + +int +hls_to_rgb(int hue, int lum, int sat) +{ + double hs = (hue + 240) % 360; + double hv = hs / 360.0; + double lv = lum / 100.0; + double sv = sat / 100.0; + double c, x, m, c2; + double r1, g1, b1; + int r, g, b; + int hpi; + + if (sat == 0) { + r = g = b = lum * 255 / 100; + return SIXEL_RGB(r, g, b); + } + + if ((c2 = ((2.0 * lv) - 1.0)) < 0.0) { + c2 = -c2; + } + c = (1.0 - c2) * sv; + hpi = (int) (hv * 6.0); + x = (hpi & 1) ? c : 0.0; + m = lv - 0.5 * c; + + switch (hpi) { + case 0: + r1 = c; + g1 = x; + b1 = 0.0; + break; + case 1: + r1 = x; + g1 = c; + b1 = 0.0; + break; + case 2: + r1 = 0.0; + g1 = c; + b1 = x; + break; + case 3: + r1 = 0.0; + g1 = x; + b1 = c; + break; + case 4: + r1 = x; + g1 = 0.0; + b1 = c; + break; + case 5: + r1 = c; + g1 = 0.0; + b1 = x; + break; + default: + return SIXEL_RGB(255, 255, 255); + } + + r = (int) ((r1 + m) * 100.0 + 0.5); + g = (int) ((g1 + m) * 100.0 + 0.5); + b = (int) ((b1 + m) * 100.0 + 0.5); + + if (r < 0) { + r = 0; + } else if (r > 100) { + r = 100; + } + if (g < 0) { + g = 0; + } else if (g > 100) { + g = 100; + } + if (b < 0) { + b = 0; + } else if (b > 100) { + b = 100; + } + return SIXEL_RGB(r * 255 / 100, g * 255 / 100, b * 255 / 100); +} diff --git a/.config/suckless/st/sixel_hls.h b/.config/suckless/st/sixel_hls.h new file mode 100644 index 0000000..6176589 --- /dev/null +++ b/.config/suckless/st/sixel_hls.h @@ -0,0 +1,7 @@ +/* + * Primary color hues: + * blue: 0 degrees + * red: 120 degrees + * green: 240 degrees + */ +int hls_to_rgb(int hue, int lum, int sat); diff --git a/.config/suckless/st/st.c b/.config/suckless/st/st.c index 6705596..5cf4ac3 100644 --- a/.config/suckless/st/st.c +++ b/.config/suckless/st/st.c @@ -21,6 +21,8 @@ #include "st.h" #include "win.h" +#include "sixel.h" + #if defined(__linux) #include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) @@ -54,6 +56,9 @@ enum term_mode { MODE_ECHO = 1 << 4, MODE_PRINT = 1 << 5, MODE_UTF8 = 1 << 6, + MODE_SIXEL = 1 << 7, + MODE_SIXEL_CUR_RT = 1 << 8, + MODE_SIXEL_SDM = 1 << 9 }; enum scroll_mode { @@ -91,6 +96,7 @@ enum escape_state { ESC_STR_END = 16, /* a final string was encountered */ ESC_TEST = 32, /* Enter in test mode */ ESC_UTF8 = 64, + ESC_DCS =128, }; typedef struct { @@ -141,6 +147,7 @@ static void ttywriteraw(const char *, size_t); static void csidump(void); static void csihandle(void); +static void dcshandle(void); static void csiparse(void); static void csireset(void); static void osc_color_response(int, int, int); @@ -156,6 +163,7 @@ static void tdumpline(int); static void tdump(void); static void tcursor(int); static void tresetcursor(void); +static void tdeleteimages(void); static void tdeleteline(int); static void tinsertblank(int); static void tinsertblankline(int); @@ -199,6 +207,7 @@ static STREscape strescseq; static int iofd = 1; static int cmdfd; static pid_t pid; +sixel_state_t sixel_st; static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; @@ -885,6 +894,7 @@ treset(void) for (y = 0; y < term.row; y++) for (x = 0; x < term.col; x++) tclearglyph(&term.line[y][x], 0); + tdeleteimages(); tswapscreen(); } tfulldirt(); @@ -1004,6 +1014,17 @@ tinsertblankline(int n) tscrolldown(term.c.y, n); } +void +tdeleteimages(void) +{ + ImageList *im, *next; + + for (im = term.images; im; im = next) { + next = im->next; + delete_image(im); + } +} + void tdeleteline(int n) { @@ -1276,6 +1297,12 @@ tsetmode(int priv, int set, const int *args, int narg) and can be mistaken for other control codes. */ break; + case 80: /* DECSDM -- Sixel Display Mode */ + MODBIT(term.mode, set, MODE_SIXEL_SDM); + break; + case 8452: /* sixel scrolling leaves cursor to right of graphic */ + MODBIT(term.mode, set, MODE_SIXEL_CUR_RT); + break; default: fprintf(stderr, "erresc: unknown private set/reset mode %d\n", @@ -1313,6 +1340,8 @@ csihandle(void) { char buffer[40]; int n = 0, len; + ImageList *im, *next; + int pi, pa; int x; int maxcol = term.col; @@ -1424,6 +1453,7 @@ csihandle(void) case 2: /* screen */ if (IS_SET(MODE_ALTSCREEN)) { tclearregion(0, 0, term.col-1, term.row-1, 1); + tdeleteimages(); break; } /* vte does this: @@ -1431,6 +1461,8 @@ csihandle(void) /* alacritty does this: */ for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--) ; + for (im = term.images; im; im = im->next) + n = MAX(im->y - term.scr, n); if (n >= 0) tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); @@ -1443,6 +1475,15 @@ csihandle(void) term.scr = 0; term.histi = 0; term.histf = 0; + for (im = term.images; im; im = next) { + next = im->next; + if (im->y < 0) + delete_image(im); + } + break; + case 6: /* sixels */ + tdeleteimages(); + tfulldirt(); break; default: goto unknown; @@ -1463,6 +1504,29 @@ csihandle(void) break; case 'S': /* SU -- Scroll line up ; XTSMGRAPHICS */ if (csiescseq.priv) { + if (csiescseq.narg > 1) { + /* XTSMGRAPHICS */ + pi = csiescseq.arg[0]; + pa = csiescseq.arg[1]; + if (pi == 1 && (pa == 1 || pa == 2 || pa == 4)) { + /* number of sixel color registers */ + /* (read, reset and read the maximum value give the same response) */ + n = snprintf(buffer, sizeof buffer, "\033[?1;0;%dS", DECSIXEL_PALETTE_MAX); + ttywrite(buffer, n, 1); + break; + } else if (pi == 2 && (pa == 1 || pa == 2 || pa == 4)) { + /* sixel graphics geometry (in pixels) */ + /* (read, reset and read the maximum value give the same response) */ + n = snprintf(buffer, sizeof buffer, "\033[?2;0;%d;%dS", + MIN(term.col * win.cw, DECSIXEL_WIDTH_MAX), + MIN(term.row * win.ch, DECSIXEL_HEIGHT_MAX)); + ttywrite(buffer, n, 1); + break; + } + /* the number of color registers and sixel geometry can't be changed */ + n = snprintf(buffer, sizeof buffer, "\033[?%d;3;0S", pi); /* failure */ + ttywrite(buffer, n, 1); + } goto unknown; } DEFAULT(csiescseq.arg[0], 1); @@ -1536,6 +1600,27 @@ csihandle(void) case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ tcursor(CURSOR_SAVE); break; + case 't': /* title stack operations ; XTWINOPS */ + switch (csiescseq.arg[0]) { + case 14: /* text area size in pixels */ + if (csiescseq.narg > 1) + goto unknown; + n = snprintf(buffer, sizeof buffer, "\033[4;%d;%dt", + term.row * win.ch, term.col * win.cw); + ttywrite(buffer, n, 1); + break; + case 16: /* character cell size in pixels */ + n = snprintf(buffer, sizeof buffer, "\033[6;%d;%dt", win.ch, win.cw); + ttywrite(buffer, n, 1); + break; + case 18: /* size of the text area in characters */ + n = snprintf(buffer, sizeof buffer, "\033[8;%d;%dt", term.row, term.col); + ttywrite(buffer, n, 1); + break; + default: + goto unknown; + } + break; case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ tcursor(CURSOR_LOAD); break; @@ -1617,6 +1702,11 @@ strhandle(void) { defaultbg, "background" }, { defaultcs, "cursor" } }; + ImageList *im, *newimages, *next, *tail; + int i, x, y, x1, y1, x2, y2, numimages; + int cx, cy; + Line line; + int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; term.esc &= ~(ESC_STR_END|ESC_STR); strparse(); @@ -1701,6 +1791,74 @@ strhandle(void) xsettitle(strescseq.args[0]); return; case 'P': /* DCS -- Device Control String */ + if (IS_SET(MODE_SIXEL)) { + term.mode &= ~MODE_SIXEL; + if (!sixel_st.image.data) { + sixel_parser_deinit(&sixel_st); + return; + } + cx = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.x; + cy = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.y; + if ((numimages = sixel_parser_finalize(&sixel_st, &newimages, + cx, cy + scr, win.cw, win.ch)) <= 0) { + sixel_parser_deinit(&sixel_st); + perror("sixel_parser_finalize() failed"); + return; + } + sixel_parser_deinit(&sixel_st); + x1 = newimages->x; + y1 = newimages->y; + x2 = x1 + newimages->cols; + y2 = y1 + numimages; + if (newimages->transparent) { + for (tail = term.images; tail && tail->next; tail = tail->next); + } else { + for (tail = NULL, im = term.images; im; im = next) { + next = im->next; + if (im->x >= x1 && im->x + im->cols <= x2 && + im->y >= y1 && im->y <= y2) { + delete_image(im); + continue; + } + tail = im; + } + } + if (tail) { + tail->next = newimages; + newimages->prev = tail; + } else { + term.images = newimages; + } + x2 = MIN(x2, term.col); + for (i = 0, im = newimages; im; im = next, i++) { + next = im->next; + scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; + if (IS_SET(MODE_SIXEL_SDM)) { + if (i >= term.row) { + delete_image(im); + continue; + } + im->y = i + scr; + line = term.line[i]; + } else { + im->y = term.c.y + scr; + line = term.line[term.c.y]; + } + for (x = im->x; x < x2; x++) { + line[x].mode |= ATTR_SIXEL; + } + term.dirty[MIN(im->y, term.row-1)] = 1; + if (!IS_SET(MODE_SIXEL_SDM) && i < numimages-1) { + im->next = NULL; + tnewline(0); + im->next = next; + } + } + /* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */ + if (!IS_SET(MODE_SIXEL_SDM) && IS_SET(MODE_SIXEL_CUR_RT)) + term.c.x = MIN(term.c.x + newimages->cols, term.col-1); + } + return; case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ return; @@ -1879,10 +2037,12 @@ tdectest(char c) void tstrsequence(uchar c) { + strreset(); switch (c) { case 0x90: /* DCS -- Device Control String */ c = 'P'; + term.esc |= ESC_DCS; break; case 0x9f: /* APC -- Application Program Command */ c = '_'; @@ -1894,7 +2054,6 @@ tstrsequence(uchar c) c = ']'; break; } - strreset(); strescseq.type = c; term.esc |= ESC_STR; } @@ -1997,6 +2156,38 @@ tcontrolcode(uchar ascii) term.esc &= ~(ESC_STR_END|ESC_STR); } +void +dcshandle(void) +{ + int bgcolor, transparent; + unsigned char r, g, b, a = 255; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case 'q': /* DECSIXEL */ + transparent = (csiescseq.narg >= 2 && csiescseq.arg[1] == 1); + if (IS_TRUECOL(term.c.attr.bg)) { + r = term.c.attr.bg >> 16 & 255; + g = term.c.attr.bg >> 8 & 255; + b = term.c.attr.bg >> 0 & 255; + } else { + xgetcolor(term.c.attr.bg, &r, &g, &b); + if (term.c.attr.bg == defaultbg) + a = dc.col[defaultbg].pixel >> 24 & 255; + } + bgcolor = a << 24 | r << 16 | g << 8 | b; + if (sixel_parser_init(&sixel_st, transparent, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0) + perror("sixel_parser_init() failed"); + term.mode |= MODE_SIXEL; + break; + } +} + /* * returns 1 when the sequence is finished and it hasn't to read * more characters for this sequence, otherwise 0 @@ -2015,6 +2206,7 @@ eschandle(uchar ascii) term.esc |= ESC_UTF8; return 0; case 'P': /* DCS -- Device Control String */ + term.esc |= ESC_DCS; case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ case ']': /* OSC -- Operating System Command */ @@ -2118,11 +2310,14 @@ tputc(Rune u) if (term.esc & ESC_STR) { if (u == '\a' || u == 030 || u == 032 || u == 033 || ISCONTROLC1(u)) { - term.esc &= ~(ESC_START|ESC_STR); + term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); term.esc |= ESC_STR_END; goto check_control_code; } + if (term.esc & ESC_DCS) + goto check_control_code; + if (strescseq.len+len >= strescseq.siz) { /* * Here is a bug in terminals. If the user never sends @@ -2176,6 +2371,15 @@ check_control_code: csihandle(); } return; + } else if (term.esc & ESC_DCS) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + csiparse(); + dcshandle(); + } + return; } else if (term.esc & ESC_UTF8) { tdefutf8(u); } else if (term.esc & ESC_ALTCHARSET) { @@ -2249,7 +2453,10 @@ twrite(const char *buf, int buflen, int show_ctrl) int n; for (n = 0; n < buflen; n += charsize) { - if (IS_SET(MODE_UTF8)) + if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) { + charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n); + continue; + } else if (IS_SET(MODE_UTF8)) { /* process a complete utf8 char */ charsize = utf8decode(buf + n, &u, buflen - n); diff --git a/.config/suckless/st/st.h b/.config/suckless/st/st.h index e5c995b..7180f73 100644 --- a/.config/suckless/st/st.h +++ b/.config/suckless/st/st.h @@ -45,9 +45,26 @@ enum glyph_attribute { ATTR_WDUMMY = 1 << 11, ATTR_BOXDRAW = 1 << 13, ATTR_LIGA = 1 << 15, + ATTR_SIXEL = 1 << 16, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; +typedef struct _ImageList { + struct _ImageList *next, *prev; + unsigned char *pixels; + void *pixmap; + void *clipmask; + int width; + int height; + int x; + int y; + int reflow_y; + int cols; + int cw; + int ch; + int transparent; +} ImageList; + enum drawing_mode { DRAW_NONE = 0, DRAW_BG = 1 << 0, @@ -135,6 +152,8 @@ typedef struct { int charset; /* current charset */ int icharset; /* selected charset for sequence */ int *tabs; + ImageList *images; /* sixel images */ + ImageList *images_alt; /* sixel images for alternate screen */ Rune lastc; /* last printed char outside of sequence, 0 if control */ } Term; diff --git a/.config/suckless/st/x.c b/.config/suckless/st/x.c index e87ea3d..ad68cbb 100644 --- a/.config/suckless/st/x.c +++ b/.config/suckless/st/x.c @@ -21,6 +21,9 @@ char *argv0; #include "win.h" #include "hb.h" +#include +#include "sixel.h" + /* X modifiers */ #define XK_ANY_MOD UINT_MAX #define XK_NO_MOD 0 @@ -226,17 +229,29 @@ zoom(const Arg *arg) Arg larg; larg.f = usedfontsize + arg->f; - zoomabs(&larg); + if (larg.f >= 1.0) + zoomabs(&larg); } void zoomabs(const Arg *arg) { + ImageList *im; xunloadfonts(); xloadfonts(usedfont, arg->f); xloadsparefonts(); + /* deleting old pixmaps forces the new scaled pixmaps to be created */ + for (im = term.images; im; im = im->next) { + if (im->pixmap) + XFreePixmap(xw.dpy, (Drawable)im->pixmap); + if (im->clipmask) + XFreePixmap(xw.dpy, (Drawable)im->clipmask); + im->pixmap = NULL; + im->clipmask = NULL; + } + cresize(0, 0); redraw(); xhints(); @@ -1761,6 +1776,110 @@ xdrawline(Line line, int x1, int y1, int x2) void xfinishdraw(void) { + ImageList *im, *next; + Imlib_Image origin, scaled; + XGCValues gcvalues; + GC gc; + int width, height; + int x, x2, del, destx, desty; + Line line; + + for (im = term.images; im; im = next) { + next = im->next; + + /* do not draw or process the image, if it is not visible */ + if (im->x >= term.col || im->y >= term.row || im->y < 0) + continue; + + /* scale the image */ + width = MAX(im->width * win.cw / im->cw, 1); + height = MAX(im->height * win.ch / im->ch, 1); + if (!im->pixmap) { + im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, width, height, + xw.depth + ); + if (!im->pixmap) + continue; + if (win.cw == im->cw && win.ch == im->ch) { + XImage ximage = { + .format = ZPixmap, + .data = (char *)im->pixels, + .width = im->width, + .height = im->height, + .xoffset = 0, + .byte_order = sixelbyteorder, + .bitmap_bit_order = MSBFirst, + .bits_per_pixel = 32, + .bytes_per_line = im->width * 4, + .bitmap_unit = 32, + .bitmap_pad = 32, + .depth = xw.depth + }; + XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height); + if (im->transparent) + im->clipmask = (void *)sixel_create_clipmask((char *)im->pixels, width, height); + } else { + origin = imlib_create_image_using_data(im->width, im->height, (DATA32 *)im->pixels); + if (!origin) + continue; + imlib_context_set_image(origin); + imlib_image_set_has_alpha(1); + imlib_context_set_anti_alias(im->transparent ? 0 : 1); /* anti-aliasing messes up the clip mask */ + scaled = imlib_create_cropped_scaled_image(0, 0, im->width, im->height, width, height); + imlib_free_image_and_decache(); + if (!scaled) + continue; + imlib_context_set_image(scaled); + imlib_image_set_has_alpha(1); + XImage ximage = { + .format = ZPixmap, + .data = (char *)imlib_image_get_data_for_reading_only(), + .width = width, + .height = height, + .xoffset = 0, + .byte_order = sixelbyteorder, + .bitmap_bit_order = MSBFirst, + .bits_per_pixel = 32, + .bytes_per_line = width * 4, + .bitmap_unit = 32, + .bitmap_pad = 32, + .depth = xw.depth + }; + XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height); + if (im->transparent) + im->clipmask = (void *)sixel_create_clipmask((char *)imlib_image_get_data_for_reading_only(), width, height); + imlib_free_image_and_decache(); + } + } + + /* clip the image so it does not go over to borders */ + x2 = MIN(im->x + im->cols, term.col); + width = MIN(width, (x2 - im->x) * win.cw); + + /* delete the image if the text cells behind it have been changed */ + line = TLINE(im->y); + for (del = 0, x = im->x; x < x2; x++) { + if ((del = !(line[x].mode & ATTR_SIXEL))) + break; + } + if (del) { + delete_image(im); + continue; + } + + /* draw the image */ + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); + destx = borderpx + im->x * win.cw; + desty = borderpx + im->y * win.ch; + if (im->clipmask) { + XSetClipMask(xw.dpy, gc, (Drawable)im->clipmask); + XSetClipOrigin(xw.dpy, gc, destx, desty); + } + XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, width, height, destx, desty); + XFreeGC(xw.dpy, gc); + } XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0); XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);