diff options
| author | twells46 <173561638+twells46@users.noreply.github.com> | 2025-11-29 08:18:48 -0600 |
|---|---|---|
| committer | twells46 <173561638+twells46@users.noreply.github.com> | 2025-11-29 08:18:48 -0600 |
| commit | 9d70dfc76159cffc6d85530ce7d9897118d9677c (patch) | |
| tree | e2171e954cdeed661b8e8af3500992d9b656f96e /patches/8-sixel.patch | |
| parent | b1c93ae35772f4fb3a923605fefa02bf1fee585e (diff) | |
Add sixel patch
Diffstat (limited to 'patches/8-sixel.patch')
| -rw-r--r-- | patches/8-sixel.patch | 3330 |
1 files changed, 3330 insertions, 0 deletions
diff --git a/patches/8-sixel.patch b/patches/8-sixel.patch new file mode 100644 index 0000000..eb4cccf --- /dev/null +++ b/patches/8-sixel.patch @@ -0,0 +1,3330 @@ +--- a/config.def.h 2024-12-08 12:07:57.276000000 +0100 ++++ b/config.def.h 2024-12-08 12:09:05.244000000 +0100 +@@ -23,7 +23,10 @@ + 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; +--- a/config.mk 2024-12-08 11:45:30.869000000 +0100 ++++ b/config.mk 2024-12-08 11:44:21.560000000 +0100 +@@ -12,11 +12,14 @@ + + PKG_CONFIG = pkg-config + ++SIXEL_C = sixel.c sixel_hls.c ++SIXEL_LIBS = `$(PKG_CONFIG) --libs imlib2` ++ + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft ${SIXEL_LIBS}\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +@@ -28,8 +31,8 @@ + # OpenBSD: + #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE + #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +-# `$(PKG_CONFIG) --libs fontconfig` \ +-# `$(PKG_CONFIG) --libs freetype2` ++# `pkg-config --libs fontconfig` \ ++# `pkg-config --libs freetype2` + #MANPREFIX = ${PREFIX}/man + + # compiler and linker +--- a/Makefile 2024-12-07 22:14:30.919000000 +0100 ++++ b/Makefile 2024-12-07 22:37:23.124000000 +0100 +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c $(SIXEL_C) + OBJ = $(SRC:.c=.o) + + all: st +--- /dev/null 2024-12-07 21:24:28.020000000 +0100 ++++ b/sixel.c 2024-12-07 22:30:35.271000000 +0100 +@@ -0,0 +1,690 @@ ++// 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 <stdlib.h> ++#include <string.h> /* 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 = 0; ++ ++ 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, color; ++ int w, h; ++ int i, j, cols, numimages; ++ char trans; ++ 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; ++ } ++ 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 (trans = 0, j = 0; j < im->height && y < h; j++, y++) { ++ src = st->image.data + image->width * y; ++ for (x = 0; x < w; x++) { ++ color = st->image.palette[*src++]; ++ trans |= (color == 0); ++ *dst++ = color; ++ } ++ } ++ im->transparent = (st->transparent && trans); ++ } ++ ++ 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; ++} +--- /dev/null 2024-12-07 21:24:28.020000000 +0100 ++++ b/sixel.h 2024-12-07 22:30:35.270000000 +0100 +@@ -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 +--- /dev/null 2024-12-07 21:24:28.020000000 +0100 ++++ b/sixel_hls.c 2024-12-07 22:30:35.271000000 +0100 +@@ -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); ++} +--- /dev/null 2024-12-07 21:24:28.020000000 +0100 ++++ b/sixel_hls.h 2024-12-07 22:30:35.271000000 +0100 +@@ -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); +--- a/st.c 2024-12-07 22:14:30.920000000 +0100 ++++ b/st.c 2024-12-07 22:30:35.483000000 +0100 +@@ -14,12 +14,15 @@ + #include <sys/types.h> + #include <sys/wait.h> + #include <termios.h> ++#include <time.h> + #include <unistd.h> + #include <wchar.h> + + #include "st.h" + #include "win.h" + ++#include "sixel.h" ++ + #if defined(__linux) + #include <pty.h> + #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +@@ -35,22 +38,27 @@ + #define ESC_ARG_SIZ 16 + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ ++#define STR_TERM_ST "\033\\" ++#define STR_TERM_BEL "\007" + + /* macros */ +-#define IS_SET(flag) ((term.mode & (flag)) != 0) +-#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +-#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +-#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +-#define ISDELIM(u) (u && wcschr(worddelimiters, u)) ++#define IS_SET(flag) ((term.mode & (flag)) != 0) ++#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) ++#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) ++#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) ++#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + + enum term_mode { +- MODE_WRAP = 1 << 0, +- MODE_INSERT = 1 << 1, +- MODE_ALTSCREEN = 1 << 2, +- MODE_CRLF = 1 << 3, +- MODE_ECHO = 1 << 4, +- MODE_PRINT = 1 << 5, +- MODE_UTF8 = 1 << 6, ++ MODE_WRAP = 1 << 0, ++ MODE_INSERT = 1 << 1, ++ MODE_ALTSCREEN = 1 << 2, ++ MODE_CRLF = 1 << 3, ++ 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 cursor_movement { +@@ -82,16 +90,10 @@ + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, ++ ESC_DCS =128, + }; + + typedef struct { +- Glyph attr; /* current char attributes */ +- int x; +- int y; +- char state; +-} TCursor; +- +-typedef struct { + int mode; + int type; + int snap; +@@ -109,27 +111,6 @@ + int alt; + } Selection; + +-/* Internal representation of the screen */ +-typedef struct { +- int row; /* nb row */ +- int col; /* nb col */ +- Line *line; /* screen */ +- Line *alt; /* alternate screen */ +- int *dirty; /* dirtyness of lines */ +- TCursor c; /* cursor */ +- int ocx; /* old cursor col */ +- int ocy; /* old cursor row */ +- int top; /* top scroll limit */ +- int bot; /* bottom scroll limit */ +- int mode; /* terminal mode flags */ +- int esc; /* escape state flags */ +- char trantbl[4]; /* charset table translation */ +- int charset; /* current charset */ +- int icharset; /* selected charset for sequence */ +- int *tabs; +- Rune lastc; /* last printed char outside of sequence, 0 if control */ +-} Term; +- + /* CSI Escape sequence structs */ + /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ + typedef struct { +@@ -150,6 +131,7 @@ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ ++ char *term; /* terminator: ST or BEL */ + } STREscape; + + static void execsh(char *, char **); +@@ -159,6 +141,7 @@ + + 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); +@@ -174,7 +157,9 @@ + static void tdump(void); + static void tclearregion(int, int, int, int); + static void tcursor(int); ++static void tresetcursor(void); + static void tdeletechar(int); ++static void tdeleteimages(void); + static void tdeleteline(int); + static void tinsertblank(int); + static void tinsertblankline(int); +@@ -191,27 +176,24 @@ + static void tsetchar(Rune, const Glyph *, int, int); + static void tsetdirt(int, int); + static void tsetscroll(int, int); ++static inline void tsetsixelattr(Line line, int x1, int x2); + static void tswapscreen(void); + static void tsetmode(int, int, const int *, int); + static int twrite(const char *, int, int); +-static void tfulldirt(void); + static void tcontrolcode(uchar ); + static void tdectest(char ); + static void tdefutf8(char); + static int32_t tdefcolor(const int *, int *, int); + static void tdeftran(char); + static void tstrsequence(uchar); +- +-static void drawregion(int, int, int, int); +- + static void selnormalize(void); + static void selscroll(int, int); + static void selsnap(int *, int *, int); + + static size_t utf8decode(const char *, Rune *, size_t); +-static Rune utf8decodebyte(char, size_t *); +-static char utf8encodebyte(Rune, size_t); +-static size_t utf8validate(Rune *, size_t); ++static inline Rune utf8decodebyte(char, size_t *); ++static inline char utf8encodebyte(Rune, size_t); ++static inline size_t utf8validate(Rune *, size_t); + + static char *base64dec(const char *); + static char base64dec_getc(const char **); +@@ -219,19 +201,20 @@ + static ssize_t xwrite(int, const char *, size_t); + + /* Globals */ +-static Term term; + static Selection sel; + static CSIEscape csiescseq; + 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}; + static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; + static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + ++ + ssize_t + xwrite(int fd, const char *s, size_t len) + { +@@ -273,7 +256,6 @@ + xstrdup(const char *s) + { + char *p; +- + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + +@@ -283,24 +265,27 @@ + size_t + utf8decode(const char *c, Rune *u, size_t clen) + { +- size_t i, j, len, type; ++ size_t i, len; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); +- if (!BETWEEN(len, 1, UTF_SIZ)) ++ if (!BETWEEN(len, 2, UTF_SIZ)) { ++ *u = (len == 1) ? udecoded : UTF_INVALID; + return 1; +- for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { +- udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); +- if (type != 0) +- return j; + } +- if (j < len) ++ clen = MIN(clen, len); ++ for (i = 1; i < clen; ++i) { ++ if ((c[i] & 0xC0) != 0x80) ++ return i; ++ udecoded = (udecoded << 6) | (c[i] & 0x3F); ++ } ++ if (i < len) + return 0; +- *u = udecoded; +- utf8validate(u, len); ++ *u = (!BETWEEN(udecoded, utfmin[len], utfmax[len]) || BETWEEN(udecoded, 0xD800, 0xDFFF)) ++ ? UTF_INVALID : udecoded; + + return len; + } +@@ -455,8 +440,8 @@ + + sel.oe.x = col; + sel.oe.y = row; +- selnormalize(); + sel.type = type; ++ selnormalize(); + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); +@@ -485,6 +470,7 @@ + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; ++ + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; +@@ -564,15 +550,15 @@ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode +- & ATTR_WRAP)) { ++ if (!(term.line[*y-1][term.col-1].mode & ATTR_WRAP)) ++ { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode +- & ATTR_WRAP)) { ++ if (!(term.line[*y][term.col-1].mode & ATTR_WRAP)) ++ { + break; + } + } +@@ -595,7 +581,8 @@ + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ +- for (y = sel.nb.y; y <= sel.ne.y; y++) { ++ for (y = sel.nb.y; y <= sel.ne.y; y++) ++ { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; +@@ -608,6 +595,7 @@ + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } ++ + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; +@@ -628,8 +616,8 @@ + * st. + * FIXME: Fix the computer world. + */ +- if ((y < sel.ne.y || lastx >= linelen) && +- (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) ++ if ((y < sel.ne.y || lastx >= linelen) ++ && (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; +@@ -641,9 +629,15 @@ + { + if (sel.ob.x == -1) + return; ++ selremove(); ++ tsetdirt(sel.nb.y, sel.ne.y); ++} ++ ++void ++selremove(void) ++{ + sel.mode = SEL_IDLE; + sel.ob.x = -1; +- tsetdirt(sel.nb.y, sel.ne.y); + } + + void +@@ -697,6 +691,7 @@ + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); ++ setenv("COLORTERM", "truecolor", 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); +@@ -715,17 +710,16 @@ + int stat; + pid_t p; + +- if ((p = waitpid(pid, &stat, WNOHANG)) < 0) +- die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); ++ while ((p = waitpid(-1, &stat, WNOHANG)) > 0) { ++ if (p == pid) { + +- if (pid != p) +- return; +- +- if (WIFEXITED(stat) && WEXITSTATUS(stat)) +- die("child exited with status %d\n", WEXITSTATUS(stat)); +- else if (WIFSIGNALED(stat)) +- die("child terminated due to signal %d\n", WTERMSIG(stat)); +- _exit(0); ++ if (WIFEXITED(stat) && WEXITSTATUS(stat)) ++ die("child exited with status %d\n", WEXITSTATUS(stat)); ++ else if (WIFSIGNALED(stat)) ++ die("child terminated due to signal %d\n", WTERMSIG(stat)); ++ _exit(0); ++ } ++ } + } + + void +@@ -756,6 +750,7 @@ + ttynew(const char *line, char *cmd, const char *out, char **args) + { + int m, s; ++ struct sigaction sa; + + if (out) { + term.mode |= MODE_PRINT; +@@ -794,7 +789,7 @@ + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) +- close(s); ++ close(s); + #ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +@@ -808,7 +803,10 @@ + #endif + close(s); + cmdfd = m; +- signal(SIGCHLD, sigchld); ++ memset(&sa, 0, sizeof(sa)); ++ sigemptyset(&sa.sa_mask); ++ sa.sa_handler = sigchld; ++ sigaction(SIGCHLD, &sa, NULL); + break; + } + return cmdfd; +@@ -960,6 +958,12 @@ + return 0; + } + ++int ++tisaltscr(void) ++{ ++ return IS_SET(MODE_ALTSCREEN); ++} ++ + void + tsetdirt(int top, int bot) + { +@@ -988,6 +992,13 @@ + } + + void ++tsetsixelattr(Line line, int x1, int x2) ++{ ++ for (; x1 <= x2; x1++) ++ line[x1].mode |= ATTR_SIXEL; ++} ++ ++void + tfulldirt(void) + { + tsetdirt(0, term.row-1); +@@ -1008,15 +1019,18 @@ + } + + void ++tresetcursor(void) ++{ ++ term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, ++ .x = 0, .y = 0, .state = CURSOR_DEFAULT }; ++} ++ ++void + treset(void) + { + uint i; + +- term.c = (TCursor){{ +- .mode = ATTR_NULL, +- .fg = defaultfg, +- .bg = defaultbg +- }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; ++ tresetcursor(); + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) +@@ -1031,6 +1045,7 @@ + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); ++ tdeleteimages(); + tswapscreen(); + } + } +@@ -1047,9 +1062,12 @@ + tswapscreen(void) + { + Line *tmp = term.line; ++ ImageList *im = term.images; + + term.line = term.alt; + term.alt = tmp; ++ term.images = term.images_alt; ++ term.images_alt = im; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); + } +@@ -1057,8 +1075,13 @@ + void + tscrolldown(int orig, int n) + { ++ + int i; + Line temp; ++ int bot = term.bot; ++ int scr = 0; ++ int itop = orig + scr, ibot = bot + scr; ++ ImageList *im, *next; + + LIMIT(n, 0, term.bot-orig+1); + +@@ -1071,14 +1094,29 @@ + 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); ++ } ++ } ++ + selscroll(orig, n); + } + + void + tscrollup(int orig, int n) + { ++ + int i; + Line temp; ++ int bot = term.bot; ++ int scr = 0; ++ int itop = orig + scr, ibot = bot + scr; ++ ImageList *im, *next; + + LIMIT(n, 0, term.bot-orig+1); + +@@ -1091,6 +1129,16 @@ + 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 < itop) ++ delete_image(im); ++ } ++ } ++ + selscroll(orig, -n); + } + +@@ -1218,6 +1266,7 @@ + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; ++ + } + + void +@@ -1292,10 +1341,22 @@ + } + + void ++tdeleteimages(void) ++{ ++ ImageList *im, *next; ++ ++ for (im = term.images; im; im = next) { ++ next = im->next; ++ delete_image(im); ++ } ++} ++ ++void + tdeleteline(int n) + { +- if (BETWEEN(term.c.y, term.top, term.bot)) ++ if (BETWEEN(term.c.y, term.top, term.bot)) { + tscrollup(term.c.y, n); ++ } + } + + int32_t +@@ -1469,7 +1530,8 @@ + void + tsetmode(int priv, int set, const int *args, int narg) + { +- int alt; const int *lim; ++ int alt; ++ const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { +@@ -1540,8 +1602,7 @@ + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { +- tclearregion(0, 0, term.col-1, +- term.row-1); ++ tclearregion(0, 0, term.col-1, term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); +@@ -1564,6 +1625,12 @@ + 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", +@@ -1599,8 +1666,11 @@ + void + csihandle(void) + { +- char buf[40]; +- int len; ++ char buffer[40]; ++ int n = 0, len; ++ ImageList *im, *next; ++ int pi, pa; ++ int maxcol = term.col; + + switch (csiescseq.mode[0]) { + default: +@@ -1698,19 +1768,30 @@ + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ +- tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); +- if (term.c.y < term.row-1) { +- tclearregion(0, term.c.y+1, term.col-1, +- term.row-1); +- } ++ tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y); ++ if (term.c.y < term.row-1) ++ tclearregion(0, term.c.y+1, maxcol-1, term.row-1); + break; + case 1: /* above */ + if (term.c.y > 0) +- tclearregion(0, 0, term.col-1, term.c.y-1); ++ tclearregion(0, 0, maxcol-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; +- case 2: /* all */ +- tclearregion(0, 0, term.col-1, term.row-1); ++ case 2: /* screen */ ++ ++ tclearregion(0, 0, maxcol-1, term.row-1); ++ tdeleteimages(); ++ break; ++ case 3: /* scrollback */ ++ 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; +@@ -1719,19 +1800,43 @@ + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ +- tclearregion(term.c.x, term.c.y, term.col-1, +- term.c.y); ++ tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ +- tclearregion(0, term.c.y, term.col-1, term.c.y); ++ tclearregion(0, term.c.y, maxcol-1, term.c.y); + break; + } + break; +- case 'S': /* SU -- Scroll <n> line up */ +- if (csiescseq.priv) break; ++ case 'S': /* SU -- Scroll <n> 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); + tscrollup(term.top, csiescseq.arg[0]); + break; +@@ -1779,9 +1884,9 @@ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */ +- len = snprintf(buf, sizeof(buf), "\033[%i;%iR", ++ len = snprintf(buffer, sizeof(buffer), "\033[%i;%iR", + term.c.y+1, term.c.x+1); +- ttywrite(buf, len, 0); ++ ttywrite(buffer, len, 0); + break; + default: + goto unknown; +@@ -1800,6 +1905,27 @@ + 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; +@@ -1860,8 +1986,8 @@ + return; + } + +- n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", +- is_osc4 ? "4;" : "", num, r, r, g, g, b, b); ++ n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x%s", ++ is_osc4 ? "4;" : "", num, r, r, g, g, b, b, strescseq.term); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", +@@ -1881,6 +2007,11 @@ + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; ++ ImageList *im, *newimages, *next, *tail = NULL; ++ int i, x1, y1, x2, y2, y, numimages; ++ int cx, cy; ++ Line line; ++ int scr = 0; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); +@@ -1914,6 +2045,8 @@ + } + } + return; ++ case 8: /* Clear Hyperlinks */ ++ return; + case 10: + case 11: + case 12: +@@ -1963,6 +2096,95 @@ + 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; ++ /* Delete the old images that are covered by the new image(s). We also need ++ * to check if they have already been deleted before adding the new ones. */ ++ if (term.images) { ++ char transparent[numimages]; ++ for (i = 0, im = newimages; im; im = im->next, i++) { ++ transparent[i] = im->transparent; ++ } ++ for (im = term.images; im; im = next) { ++ next = im->next; ++ if (im->y >= y1 && im->y < y2) { ++ y = im->y - scr; ++ if (y >= 0 && y < term.row && term.dirty[y]) { ++ line = term.line[y]; ++ j = MIN(im->x + im->cols, term.col); ++ for (i = im->x; i < j; i++) { ++ if (line[i].mode & ATTR_SIXEL) ++ break; ++ } ++ if (i == j) { ++ delete_image(im); ++ continue; ++ } ++ } ++ if (im->x >= x1 && im->x + im->cols <= x2 && !transparent[im->y - y1]) { ++ delete_image(im); ++ continue; ++ } ++ } ++ tail = im; ++ } ++ } ++ if (tail) { ++ tail->next = newimages; ++ newimages->prev = tail; ++ } else { ++ term.images = newimages; ++ } ++ x2 = MIN(x2, term.col) - 1; ++ if (IS_SET(MODE_SIXEL_SDM)) { ++ /* Sixel display mode: put the sixel in the upper left corner of ++ * the screen, disable scrolling (the sixel will be truncated if ++ * it is too long) and do not change the cursor position. */ ++ for (i = 0, im = newimages; im; im = next, i++) { ++ next = im->next; ++ if (i >= term.row) { ++ delete_image(im); ++ continue; ++ } ++ im->y = i + scr; ++ tsetsixelattr(term.line[i], x1, x2); ++ term.dirty[MIN(im->y, term.row-1)] = 1; ++ } ++ } else { ++ for (i = 0, im = newimages; im; im = next, i++) { ++ next = im->next; ++ im->y = term.c.y + scr; ++ tsetsixelattr(term.line[term.c.y], x1, x2); ++ term.dirty[MIN(im->y, term.row-1)] = 1; ++ if (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_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; +@@ -1984,6 +2206,16 @@ + if (*p == '\0') + return; + ++ /* preserve semicolons in window titles, icon names and OSC 7 sequences */ ++ if (strescseq.type == ']' && ( ++ p[0] <= '2' ++ ) && p[1] == ';') { ++ strescseq.args[strescseq.narg++] = p; ++ strescseq.args[strescseq.narg++] = p + 2; ++ p[1] = '\0'; ++ return; ++ } ++ + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') +@@ -2018,7 +2250,7 @@ + fprintf(stderr, "(%02x)", c); + } + } +- fprintf(stderr, "ESC\\\n"); ++ fprintf(stderr, (strescseq.term[0] == 0x1b) ? "ESC\\\n" : "BEL\n"); + } + + void +@@ -2156,9 +2388,12 @@ + 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 = '_'; +@@ -2170,7 +2405,6 @@ + c = ']'; + break; + } +- strreset(); + strescseq.type = c; + term.esc |= ESC_STR; + } +@@ -2197,6 +2431,7 @@ + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ ++ strescseq.term = STR_TERM_BEL; + strhandle(); + } else { + xbell(); +@@ -2272,6 +2507,38 @@ + 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 +@@ -2290,6 +2557,7 @@ + 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 */ +@@ -2349,8 +2617,10 @@ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ +- if (term.esc & ESC_STR_END) ++ if (term.esc & ESC_STR_END) { ++ strescseq.term = STR_TERM_ST; + strhandle(); ++ } + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", +@@ -2369,7 +2639,8 @@ + Glyph *gp; + + control = ISCONTROL(u); +- if (u < 127 || !IS_SET(MODE_UTF8)) { ++ if (u < 127 || !IS_SET(MODE_UTF8)) ++ { + c[0] = u; + width = len = 1; + } else { +@@ -2390,11 +2661,14 @@ + 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 +@@ -2448,6 +2722,15 @@ + 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) { +@@ -2466,6 +2749,7 @@ + */ + return; + } ++ + if (selected(term.c.x, term.c.y)) + selclear(); + +@@ -2518,7 +2802,11 @@ + 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); + if (charsize == 0) +@@ -2545,11 +2833,13 @@ + void + tresize(int col, int row) + { +- int i; ++ int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; +- TCursor c; ++ int x2; ++ Line line; ++ ImageList *im, *next; + + if (col < 1 || row < 1) { + fprintf(stderr, +@@ -2557,23 +2847,19 @@ + return; + } + +- /* +- * slide screen to keep cursor where we expect it - +- * tscrollup would work here, but we can optimize to +- * memmove because we're freeing the earlier lines +- */ +- for (i = 0; i <= term.c.y - row; i++) { +- free(term.line[i]); +- free(term.alt[i]); +- } +- /* ensure that both src and dst are not NULL */ +- if (i > 0) { +- memmove(term.line, term.line + i, row * sizeof(Line)); +- memmove(term.alt, term.alt + i, row * sizeof(Line)); +- } +- for (i += row; i < term.row; i++) { +- free(term.line[i]); +- free(term.alt[i]); ++ /* scroll both screens independently */ ++ if (row < term.row) { ++ tcursor(CURSOR_SAVE); ++ tsetscroll(0, term.row - 1); ++ for (i = 0; i < 2; i++) { ++ if (term.c.y >= row) { ++ tscrollup(0, term.c.y - row + 1); ++ } ++ for (j = row; j < term.row; j++) ++ free(term.line[j]); ++ tswapscreen(); ++ tcursor(CURSOR_LOAD); ++ } + } + + /* resize to new height */ +@@ -2593,25 +2879,27 @@ + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } +- if (col > term.col) { ++ if (col > term.col) ++ { + bp = term.tabs + term.col; +- + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); ++ + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } ++ + /* update terminal size */ + term.col = col; + term.row = row; ++ + /* reset scrolling region */ + tsetscroll(0, row-1); +- /* make use of the LIMIT in tmoveto */ +- tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ +- c = term.c; + for (i = 0; i < 2; i++) { ++ tmoveto(term.c.x, term.c.y); /* make use of the LIMIT in tmoveto */ ++ tcursor(CURSOR_SAVE); + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } +@@ -2621,7 +2909,22 @@ + tswapscreen(); + tcursor(CURSOR_LOAD); + } +- term.c = c; ++ ++ /* expand images into new text cells */ ++ for (i = 0; i < 2; i++) { ++ for (im = term.images; im; im = next) { ++ next = im->next; ++ if (im->y < 0 || im->y >= term.row) { ++ delete_image(im); ++ continue; ++ } ++ line = term.line[im->y]; ++ x2 = MIN(im->x + im->cols, col) - 1; ++ if (mincol < col && x2 >= mincol && im->x < col) ++ tsetsixelattr(line, MAX(im->x, mincol), x2); ++ } ++ tswapscreen(); ++ } + } + + void +@@ -2644,6 +2947,7 @@ + } + } + ++ + void + draw(void) + { +@@ -2661,6 +2965,7 @@ + cx--; + + drawregion(0, 0, term.col, term.row); ++ + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; +--- a/st.h 2024-12-07 22:14:30.920000000 +0100 ++++ b/st.h 2024-12-07 22:30:35.271000000 +0100 +@@ -1,7 +1,14 @@ + /* See LICENSE for license details. */ + + #include <stdint.h> ++#include <time.h> + #include <sys/types.h> ++#include <X11/Xatom.h> ++#include <X11/Xlib.h> ++#include <X11/cursorfont.h> ++#include <X11/keysym.h> ++#include <X11/Xft/Xft.h> ++#include <X11/XKBlib.h> + + /* macros */ + #define MIN(a, b) ((a) < (b) ? (a) : (b)) +@@ -21,21 +28,45 @@ + #define IS_TRUECOL(x) (1 << 24 & (x)) + + enum glyph_attribute { +- ATTR_NULL = 0, +- ATTR_BOLD = 1 << 0, +- ATTR_FAINT = 1 << 1, +- ATTR_ITALIC = 1 << 2, +- ATTR_UNDERLINE = 1 << 3, +- ATTR_BLINK = 1 << 4, +- ATTR_REVERSE = 1 << 5, +- ATTR_INVISIBLE = 1 << 6, +- ATTR_STRUCK = 1 << 7, +- ATTR_WRAP = 1 << 8, +- ATTR_WIDE = 1 << 9, +- ATTR_WDUMMY = 1 << 10, ++ ATTR_NULL = 0, ++ ATTR_SET = 1 << 0, ++ ATTR_BOLD = 1 << 1, ++ ATTR_FAINT = 1 << 2, ++ ATTR_ITALIC = 1 << 3, ++ ATTR_UNDERLINE = 1 << 4, ++ ATTR_BLINK = 1 << 5, ++ ATTR_REVERSE = 1 << 6, ++ ATTR_INVISIBLE = 1 << 7, ++ ATTR_STRUCK = 1 << 8, ++ ATTR_WRAP = 1 << 9, ++ ATTR_WIDE = 1 << 10, ++ ATTR_WDUMMY = 1 << 11, ++ 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 cols; ++ int cw; ++ int ch; ++ int transparent; ++} ImageList; ++ ++/* Used to control which screen(s) keybindings and mouse shortcuts apply to. */ ++enum screen { ++ S_PRI = -1, /* primary screen */ ++ S_ALL = 0, /* both primary and alt screen */ ++ S_ALT = 1 /* alternate screen */ ++}; ++ + enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, +@@ -59,16 +90,50 @@ + + typedef uint_least32_t Rune; + ++typedef XftDraw *Draw; ++typedef XftColor Color; ++typedef XftGlyphFontSpec GlyphFontSpec; ++ + #define Glyph Glyph_ + typedef struct { + Rune u; /* character code */ +- ushort mode; /* attribute flags */ ++ uint32_t mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ + } Glyph; + + typedef Glyph *Line; + ++typedef struct { ++ Glyph attr; /* current char attributes */ ++ int x; ++ int y; ++ char state; ++} TCursor; ++ ++/* Internal representation of the screen */ ++typedef struct { ++ int row; /* nb row */ ++ int col; /* nb col */ ++ Line *line; /* screen */ ++ Line *alt; /* alternate screen */ ++ int *dirty; /* dirtyness of lines */ ++ TCursor c; /* cursor */ ++ int ocx; /* old cursor col */ ++ int ocy; /* old cursor row */ ++ int top; /* top scroll limit */ ++ int bot; /* bottom scroll limit */ ++ int mode; /* terminal mode flags */ ++ int esc; /* escape state flags */ ++ char trantbl[4]; /* charset table translation */ ++ 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; ++ + typedef union { + int i; + uint ui; +@@ -77,9 +142,101 @@ + const char *s; + } Arg; + ++/* Purely graphic info */ ++typedef struct { ++ int tw, th; /* tty width and height */ ++ int w, h; /* window width and height */ ++ int ch; /* char height */ ++ int cw; /* char width */ ++ int mode; /* window state/mode flags */ ++ int cursor; /* cursor style */ ++} TermWindow; ++ ++typedef struct { ++ Display *dpy; ++ Colormap cmap; ++ Window win; ++ Drawable buf; ++ GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ ++ Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; ++ struct { ++ XIM xim; ++ XIC xic; ++ XPoint spot; ++ XVaNestedList spotlist; ++ } ime; ++ Draw draw; ++ Visual *vis; ++ XSetWindowAttributes attrs; ++ int scr; ++ int isfixed; /* is fixed geometry? */ ++ int l, t; /* left and top offset */ ++ int gm; /* geometry mask */ ++} XWindow; ++ ++typedef struct { ++ Atom xtarget; ++ char *primary, *clipboard; ++ struct timespec tclick1; ++ struct timespec tclick2; ++} XSelection; ++ ++/* types used in config.h */ ++typedef struct { ++ uint mod; ++ KeySym keysym; ++ void (*func)(const Arg *); ++ const Arg arg; ++ int screen; ++} Shortcut; ++ ++typedef struct { ++ uint mod; ++ uint button; ++ void (*func)(const Arg *); ++ const Arg arg; ++ uint release; ++ int screen; ++} MouseShortcut; ++ ++typedef struct { ++ KeySym k; ++ uint mask; ++ char *s; ++ /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ ++ signed char appkey; /* application keypad */ ++ signed char appcursor; /* application cursor */ ++} Key; ++ ++/* Font structure */ ++#define Font Font_ ++typedef struct { ++ int height; ++ int width; ++ int ascent; ++ int descent; ++ int badslant; ++ int badweight; ++ short lbearing; ++ short rbearing; ++ XftFont *match; ++ FcFontSet *set; ++ FcPattern *pattern; ++} Font; ++ ++/* Drawing Context */ ++typedef struct { ++ Color *col; ++ size_t collen; ++ Font font, bfont, ifont, ibfont; ++ GC gc; ++} DC; ++ + void die(const char *, ...); + void redraw(void); + void draw(void); ++void drawregion(int, int, int, int); ++void tfulldirt(void); + + void printscreen(const Arg *); + void printsel(const Arg *); +@@ -87,6 +244,7 @@ + void toggleprinter(const Arg *); + + int tattrset(int); ++int tisaltscr(void); + void tnew(int, int); + void tresize(int, int); + void tsetdirtattr(int); +@@ -100,6 +258,7 @@ + + void selclear(void); + void selinit(void); ++void selremove(void); + void selstart(int, int, int); + void selextend(int, int, int, int); + int selected(int, int); +@@ -111,6 +270,8 @@ + void *xrealloc(void *, size_t); + char *xstrdup(const char *); + ++int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); ++ + /* config.h globals */ + extern char *utmp; + extern char *scroll; +@@ -124,3 +285,9 @@ + extern unsigned int defaultfg; + extern unsigned int defaultbg; + extern unsigned int defaultcs; ++ ++extern DC dc; ++extern XWindow xw; ++extern XSelection xsel; ++extern TermWindow win; ++extern Term term; +--- a/x.c 2024-12-07 22:42:04.400000000 +0100 ++++ b/x.c 2024-12-07 22:30:35.466000000 +0100 +@@ -20,30 +20,8 @@ + #include "st.h" + #include "win.h" + +-/* types used in config.h */ +-typedef struct { +- uint mod; +- KeySym keysym; +- void (*func)(const Arg *); +- const Arg arg; +-} Shortcut; +- +-typedef struct { +- uint mod; +- uint button; +- void (*func)(const Arg *); +- const Arg arg; +- uint release; +-} MouseShortcut; +- +-typedef struct { +- KeySym k; +- uint mask; +- char *s; +- /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ +- signed char appkey; /* application keypad */ +- signed char appcursor; /* application cursor */ +-} Key; ++#include <Imlib2.h> ++#include "sixel.h" + + /* X modifiers */ + #define XK_ANY_MOD UINT_MAX +@@ -55,10 +33,11 @@ + static void clippaste(const Arg *); + static void numlock(const Arg *); + static void selpaste(const Arg *); ++static void ttysend(const Arg *); + static void zoom(const Arg *); + static void zoomabs(const Arg *); + static void zoomreset(const Arg *); +-static void ttysend(const Arg *); ++ + + /* config.h for applying patches and the configuration. */ + #include "config.h" +@@ -73,77 +52,10 @@ + #define TRUEGREEN(x) (((x) & 0xff00)) + #define TRUEBLUE(x) (((x) & 0xff) << 8) + +-typedef XftDraw *Draw; +-typedef XftColor Color; +-typedef XftGlyphFontSpec GlyphFontSpec; +- +-/* Purely graphic info */ +-typedef struct { +- int tw, th; /* tty width and height */ +- int w, h; /* window width and height */ +- int ch; /* char height */ +- int cw; /* char width */ +- int mode; /* window state/mode flags */ +- int cursor; /* cursor style */ +-} TermWindow; +- +-typedef struct { +- Display *dpy; +- Colormap cmap; +- Window win; +- Drawable buf; +- GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ +- Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; +- struct { +- XIM xim; +- XIC xic; +- XPoint spot; +- XVaNestedList spotlist; +- } ime; +- Draw draw; +- Visual *vis; +- XSetWindowAttributes attrs; +- int scr; +- int isfixed; /* is fixed geometry? */ +- int l, t; /* left and top offset */ +- int gm; /* geometry mask */ +-} XWindow; +- +-typedef struct { +- Atom xtarget; +- char *primary, *clipboard; +- struct timespec tclick1; +- struct timespec tclick2; +-} XSelection; +- +-/* Font structure */ +-#define Font Font_ +-typedef struct { +- int height; +- int width; +- int ascent; +- int descent; +- int badslant; +- int badweight; +- short lbearing; +- short rbearing; +- XftFont *match; +- FcFontSet *set; +- FcPattern *pattern; +-} Font; +- +-/* Drawing Context */ +-typedef struct { +- Color *col; +- size_t collen; +- Font font, bfont, ifont, ibfont; +- GC gc; +-} DC; +- + static inline ushort sixd_to_16bit(int); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); + static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +-static void xdrawglyph(Glyph, int, int); ++void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); + static int ximopen(Display *); +@@ -172,7 +84,6 @@ + static void resize(XEvent *); + static void focus(XEvent *); + static uint buttonmask(uint); +-static int mouseaction(XEvent *, uint); + static void brelease(XEvent *); + static void bpress(XEvent *); + static void bmotion(XEvent *); +@@ -181,6 +92,7 @@ + static void selclear_(XEvent *); + static void selrequest(XEvent *); + static void setsel(char *, Time); ++static int mouseaction(XEvent *, uint); + static void mousesel(XEvent *, int); + static void mousereport(XEvent *); + static char *kmap(KeySym, uint); +@@ -216,10 +128,11 @@ + }; + + /* Globals */ +-static DC dc; +-static XWindow xw; +-static XSelection xsel; +-static TermWindow win; ++Term term; ++DC dc; ++XWindow xw; ++XSelection xsel; ++TermWindow win; + + /* Font Ring Cache */ + enum { +@@ -254,6 +167,7 @@ + + static uint buttons; /* bit field of pressed buttons */ + ++ + void + clipcopy(const Arg *dummy) + { +@@ -280,16 +194,23 @@ + } + + void ++numlock(const Arg *dummy) ++{ ++ win.mode ^= MODE_NUMLOCK; ++} ++ ++void + selpaste(const Arg *dummy) + { ++ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); + } + + void +-numlock(const Arg *dummy) ++ttysend(const Arg *arg) + { +- win.mode ^= MODE_NUMLOCK; ++ ttywrite(arg->s, strlen(arg->s), 1); + } + + void +@@ -298,14 +219,31 @@ + Arg larg; + + larg.f = usedfontsize + arg->f; +- zoomabs(&larg); ++ if (larg.f >= 1.0) ++ zoomabs(&larg); + } + + void + zoomabs(const Arg *arg) + { ++ int i; ++ ImageList *im; ++ + xunloadfonts(); + xloadfonts(usedfont, arg->f); ++ ++ /* delete old pixmaps so that xfinishdraw() can create new scaled ones */ ++ for (im = term.images, i = 0; i < 2; i++, im = term.images_alt) { ++ for (; 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(); +@@ -322,12 +260,6 @@ + } + } + +-void +-ttysend(const Arg *arg) +-{ +- ttywrite(arg->s, strlen(arg->s), 1); +-} +- + int + evcol(XEvent *e) + { +@@ -344,6 +276,40 @@ + return y / win.ch; + } + ++uint ++buttonmask(uint button) ++{ ++ return button == Button1 ? Button1Mask ++ : button == Button2 ? Button2Mask ++ : button == Button3 ? Button3Mask ++ : button == Button4 ? Button4Mask ++ : button == Button5 ? Button5Mask ++ : 0; ++} ++ ++int ++mouseaction(XEvent *e, uint release) ++{ ++ MouseShortcut *ms; ++ int screen = tisaltscr() ? S_ALT : S_PRI; ++ ++ /* ignore Button<N>mask for Button<N> - it's set on release */ ++ uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); ++ ++ for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { ++ if (ms->release == release && ++ ms->button == e->xbutton.button && ++ (!ms->screen || (ms->screen == screen)) && ++ (match(ms->mod, state) || /* exact or forced */ ++ match(ms->mod, state & ~forcemousemod))) { ++ ms->func(&(ms->arg)); ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ + void + mousesel(XEvent *e, int done) + { +@@ -378,6 +344,7 @@ + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; ++ + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) +@@ -433,38 +400,6 @@ + ttywrite(buf, len, 0); + } + +-uint +-buttonmask(uint button) +-{ +- return button == Button1 ? Button1Mask +- : button == Button2 ? Button2Mask +- : button == Button3 ? Button3Mask +- : button == Button4 ? Button4Mask +- : button == Button5 ? Button5Mask +- : 0; +-} +- +-int +-mouseaction(XEvent *e, uint release) +-{ +- MouseShortcut *ms; +- +- /* ignore Button<N>mask for Button<N> - it's set on release */ +- uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); +- +- for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { +- if (ms->release == release && +- ms->button == e->xbutton.button && +- (match(ms->mod, state) || /* exact or forced */ +- match(ms->mod, state & ~forcemousemod))) { +- ms->func(&(ms->arg)); +- return 1; +- } +- } +- +- return 0; +-} +- + void + bpress(XEvent *e) + { +@@ -500,6 +435,7 @@ + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); ++ + } + } + +@@ -515,6 +451,7 @@ + xpev->atom == clipboard)) { + selnotify(e); + } ++ + } + + void +@@ -545,7 +482,8 @@ + return; + } + +- if (e->type == PropertyNotify && nitems == 0 && rem == 0) { ++ if (e->type == PropertyNotify && nitems == 0 && rem == 0) ++ { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all +@@ -686,6 +624,7 @@ + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); ++ + } + + void +@@ -709,13 +648,17 @@ + + if (mouseaction(e, 1)) + return; +- if (btn == Button1) ++ ++ if (btn == Button1) { + mousesel(e, 1); ++ } ++ + } + + void + bmotion(XEvent *e) + { ++ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; +@@ -736,7 +679,7 @@ + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; +- col = MAX(1, col); ++ col = MAX(2, col); + row = MAX(1, row); + + tresize(col, row); +@@ -752,7 +695,8 @@ + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ DefaultDepth(xw.dpy, xw.scr) ++ ); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + +@@ -857,6 +801,12 @@ + } + + void ++xclearwin(void) ++{ ++ xclear(0, 0, win.w, win.h); ++} ++ ++void + xhints(void) + { + XClassHint class = {opt_name ? opt_name : termname, +@@ -908,6 +858,60 @@ + } + + int ++ximopen(Display *dpy) ++{ ++ XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; ++ XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; ++ ++ xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); ++ if (xw.ime.xim == NULL) ++ return 0; ++ ++ if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) ++ fprintf(stderr, "XSetIMValues: " ++ "Could not set XNDestroyCallback.\n"); ++ ++ xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, ++ NULL); ++ ++ if (xw.ime.xic == NULL) { ++ xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, ++ XIMPreeditNothing | XIMStatusNothing, ++ XNClientWindow, xw.win, ++ XNDestroyCallback, &icdestroy, ++ NULL); ++ } ++ if (xw.ime.xic == NULL) ++ fprintf(stderr, "XCreateIC: Could not create input context.\n"); ++ ++ return 1; ++} ++ ++void ++ximinstantiate(Display *dpy, XPointer client, XPointer call) ++{ ++ if (ximopen(dpy)) ++ XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ++ ximinstantiate, NULL); ++} ++ ++void ++ximdestroy(XIM xim, XPointer client, XPointer call) ++{ ++ xw.ime.xim = NULL; ++ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ++ ximinstantiate, NULL); ++ XFree(xw.ime.spotlist); ++} ++ ++int ++xicdestroy(XIC xim, XPointer client, XPointer call) ++{ ++ xw.ime.xic = NULL; ++ return 1; ++} ++ ++int + xloadfont(Font *f, FcPattern *pattern) + { + FcPattern *configured; +@@ -1062,6 +1066,7 @@ + void + xunloadfonts(void) + { ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1072,60 +1077,6 @@ + xunloadfont(&dc.ibfont); + } + +-int +-ximopen(Display *dpy) +-{ +- XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; +- XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; +- +- xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); +- if (xw.ime.xim == NULL) +- return 0; +- +- if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) +- fprintf(stderr, "XSetIMValues: " +- "Could not set XNDestroyCallback.\n"); +- +- xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, +- NULL); +- +- if (xw.ime.xic == NULL) { +- xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, +- XIMPreeditNothing | XIMStatusNothing, +- XNClientWindow, xw.win, +- XNDestroyCallback, &icdestroy, +- NULL); +- } +- if (xw.ime.xic == NULL) +- fprintf(stderr, "XCreateIC: Could not create input context.\n"); +- +- return 1; +-} +- +-void +-ximinstantiate(Display *dpy, XPointer client, XPointer call) +-{ +- if (ximopen(dpy)) +- XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, +- ximinstantiate, NULL); +-} +- +-void +-ximdestroy(XIM xim, XPointer client, XPointer call) +-{ +- xw.ime.xim = NULL; +- XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, +- ximinstantiate, NULL); +- XFree(xw.ime.spotlist); +-} +- +-int +-xicdestroy(XIC xim, XPointer client, XPointer call) +-{ +- xw.ime.xic = NULL; +- return 1; +-} +- + void + xinit(int cols, int rows) + { +@@ -1138,6 +1089,7 @@ + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); ++ + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ +@@ -1165,7 +1117,8 @@ + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask +- | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; ++ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask ++ ; + xw.attrs.colormap = xw.cmap; + + root = XRootWindow(xw.dpy, xw.scr); +@@ -1180,6 +1133,7 @@ + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; ++ + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +@@ -1196,7 +1150,7 @@ + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, +- ximinstantiate, NULL); ++ ximinstantiate, NULL); + } + + /* white cursor, black outline */ +@@ -1240,6 +1194,7 @@ + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; ++ + } + + int +@@ -1249,7 +1204,7 @@ + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; +- float runewidth = win.cw; ++ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f); + Rune rune; + FT_UInt glyphidx; + FcResult fcres; +@@ -1258,7 +1213,8 @@ + FcCharSet *fccharset; + int i, f, numspecs = 0; + +- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { ++ for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) ++ { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; +@@ -1314,8 +1270,7 @@ + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) +- font->set = FcFontSort(0, font->pattern, +- 1, 0, &fcres); ++ font->set = FcFontSort(0, font->pattern, 1, 0, &fcres); + fcsets[0] = font->set; + + /* +@@ -1329,16 +1284,13 @@ + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); +- FcPatternAddCharSet(fcpattern, FC_CHARSET, +- fccharset); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + +- FcConfigSubstitute(0, fcpattern, +- FcMatchPattern); ++ FcConfigSubstitute(0, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + +- fontpattern = FcFontSetMatch(0, fcsets, 1, +- fcpattern, &fcres); ++ fontpattern = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { +@@ -1346,8 +1298,7 @@ + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + +- frc[frclen].font = XftFontOpenPattern(xw.dpy, +- fontpattern); ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); +@@ -1375,11 +1326,11 @@ + } + + void +-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +-{ ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y ++) { + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); +- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, +- width = charlen * win.cw; ++ int width = charlen * win.cw; ++ int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; +@@ -1482,6 +1433,7 @@ + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ ++ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ +@@ -1513,10 +1465,11 @@ + xdrawglyph(Glyph g, int x, int y) + { + int numspecs; +- XftGlyphFontSpec spec; ++ XftGlyphFontSpec *specs = xw.specbuf; + +- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); +- xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); ++ xdrawglyphfontspecs(specs, g, numspecs, x, y ++ ); + } + + void +@@ -1527,6 +1480,7 @@ + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; ++ + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) +@@ -1535,7 +1489,8 @@ + /* + * Select the right color for the right mode. + */ +- g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; ++ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE ++ ; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; +@@ -1555,6 +1510,7 @@ + g.fg = defaultbg; + g.bg = defaultcs; + } ++ + drawcol = dc.col[g.bg]; + } + +@@ -1564,13 +1520,13 @@ + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ +- case 0: /* Blinking Block */ +- case 1: /* Blinking Block (Default) */ +- case 2: /* Steady Block */ ++ case 0: /* Blinking block */ ++ case 1: /* Blinking block (default) */ ++ case 2: /* Steady block */ + xdrawglyph(g, cx, cy); + break; +- case 3: /* Blinking Underline */ +- case 4: /* Steady Underline */ ++ case 3: /* Blinking underline */ ++ case 4: /* Steady underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ +@@ -1624,7 +1580,7 @@ + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, +- &prop) != Success) ++ &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); +@@ -1641,7 +1597,7 @@ + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, +- &prop) != Success) ++ &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); +@@ -1659,6 +1615,7 @@ + { + int i, x, ox, numspecs; + Glyph base, new; ++ + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); +@@ -1683,16 +1640,132 @@ + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); ++ + } + + void + xfinishdraw(void) + { +- 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); ++ ImageList *im, *next; ++ Imlib_Image origin, scaled; ++ XGCValues gcvalues; ++ GC gc = NULL; ++ int width, height; ++ int del, desty, mode, x1, x2, xend; ++ int bw = borderpx, bh = borderpx; ++ 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, ++ DefaultDepth(xw.dpy, xw.scr) ++ ); ++ 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 = 24 ++ }; ++ 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 = 24 ++ }; ++ 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(); ++ } ++ } ++ ++ /* create GC */ ++ if (!gc) { ++ memset(&gcvalues, 0, sizeof(gcvalues)); ++ gcvalues.graphics_exposures = False; ++ gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); ++ } ++ ++ /* set the clip mask */ ++ desty = bh + im->y * win.ch; ++ if (im->clipmask) { ++ XSetClipMask(xw.dpy, gc, (Drawable)im->clipmask); ++ XSetClipOrigin(xw.dpy, gc, bw + im->x * win.cw, desty); ++ } ++ ++ /* draw only the parts of the image that are not erased */ ++ line = term.line[im->y] + im->x; ++ xend = MIN(im->x + im->cols, term.col); ++ for (del = 1, x1 = im->x; x1 < xend; x1 = x2) { ++ mode = line->mode & ATTR_SIXEL; ++ for (x2 = x1 + 1; x2 < xend; x2++) { ++ if (((++line)->mode & ATTR_SIXEL) != mode) ++ break; ++ } ++ if (mode) { ++ XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, ++ (x1 - im->x) * win.cw, 0, ++ MIN((x2 - x1) * win.cw, width - (x1 - im->x) * win.cw), height, ++ bw + x1 * win.cw, desty); ++ del = 0; ++ } ++ } ++ if (im->clipmask) ++ XSetClipMask(xw.dpy, gc, None); ++ ++ /* if all the parts are erased, we can delete the entire image */ ++ if (del && im->x + im->cols <= term.col) ++ delete_image(im); ++ } ++ if (gc) ++ 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); + } + + void +@@ -1844,7 +1917,7 @@ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; +- int len; ++ int len, screen; + Rune c; + Status status; + Shortcut *bp; +@@ -1859,9 +1932,13 @@ + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } ++ ++ screen = tisaltscr() ? S_ALT : S_PRI; ++ + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { +- if (ksym == bp->keysym && match(bp->mod, e->state)) { ++ if (ksym == bp->keysym && match(bp->mod, e->state) && ++ (!bp->screen || bp->screen == screen)) { + bp->func(&(bp->arg)); + return; + } +@@ -1914,6 +1991,7 @@ + void + resize(XEvent *e) + { ++ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + +@@ -1992,7 +2070,8 @@ + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ +- if (FD_ISSET(ttyfd, &rfd) || xev) { ++ if (FD_ISSET(ttyfd, &rfd) || xev) ++ { + if (!drawing) { + trigger = now; + drawing = 1; +@@ -2005,7 +2084,8 @@ + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; +- if (blinktimeout && tattrset(ATTR_BLINK)) { ++ if (blinktimeout && tattrset(ATTR_BLINK)) ++ { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ +@@ -2026,14 +2106,16 @@ + void + usage(void) + { +- die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" +- " [-n name] [-o file]\n" +- " [-T title] [-t title] [-w windowid]" +- " [[-e] command [args ...]]\n" +- " %s [-aiv] [-c class] [-f font] [-g geometry]" +- " [-n name] [-o file]\n" +- " [-T title] [-t title] [-w windowid] -l line" +- " [stty_args ...]\n", argv0, argv0); ++ die("usage: %s [-aiv] [-c class]" ++ " [-f font] [-g geometry]" ++ " [-n name] [-o file]\n" ++ " [-T title] [-t title] [-w windowid]" ++ " [[-e] command [args ...]]\n" ++ " %s [-aiv] [-c class]" ++ " [-f font] [-g geometry]" ++ " [-n name] [-o file]\n" ++ " [-T title] [-t title] [-w windowid] -l line" ++ " [stty_args ...]\n", argv0, argv0); + } + + int +@@ -2096,6 +2178,7 @@ + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); ++ + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); |