aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--config.def.h5
-rw-r--r--config.h5
-rw-r--r--config.mk9
-rw-r--r--patches/8-sixel.patch3330
-rw-r--r--patches/st-anysize-20220718-baa9357.diff164
-rw-r--r--patches/st-expected-anysize-0.9.diff15
-rw-r--r--sixel.c690
-rw-r--r--sixel.h63
-rw-r--r--sixel_hls.c115
-rw-r--r--sixel_hls.h7
-rw-r--r--st.c581
-rw-r--r--st.h193
-rw-r--r--x.c569
14 files changed, 5348 insertions, 400 deletions
diff --git a/Makefile b/Makefile
index 15db421..899b163 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/config.def.h b/config.def.h
index 2cd740a..e824b5d 100644
--- a/config.def.h
+++ b/config.def.h
@@ -23,7 +23,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;
diff --git a/config.h b/config.h
index 43c8771..5ec2d71 100644
--- a/config.h
+++ b/config.h
@@ -23,7 +23,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;
diff --git a/config.mk b/config.mk
index a20fa78..25fdbca 100644
--- a/config.mk
+++ b/config.mk
@@ -12,11 +12,14 @@ X11LIB = /usr/X11R6/lib
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 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
# 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
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);
diff --git a/patches/st-anysize-20220718-baa9357.diff b/patches/st-anysize-20220718-baa9357.diff
new file mode 100644
index 0000000..675ffdf
--- /dev/null
+++ b/patches/st-anysize-20220718-baa9357.diff
@@ -0,0 +1,164 @@
+From 8dcdc4b21a73268e167d98aa30f24315c7f3b7ff Mon Sep 17 00:00:00 2001
+From: Bakkeby <bakkeby@gmail.com>
+Date: Mon, 18 Jul 2022 16:52:03 +0200
+Subject: [PATCH] Adding anysize patch
+
+---
+ x.c | 56 ++++++++++++++++++++++++++++++--------------------------
+ 1 file changed, 30 insertions(+), 26 deletions(-)
+
+diff --git a/x.c b/x.c
+index 2a3bd38..f534347 100644
+--- a/x.c
++++ b/x.c
+@@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
+ typedef struct {
+ int tw, th; /* tty width and height */
+ int w, h; /* window width and height */
++ int hborderpx, vborderpx;
+ int ch; /* char height */
+ int cw; /* char width */
+ int mode; /* window state/mode flags */
+@@ -331,7 +332,7 @@ ttysend(const Arg *arg)
+ int
+ evcol(XEvent *e)
+ {
+- int x = e->xbutton.x - borderpx;
++ int x = e->xbutton.x - win.hborderpx;
+ LIMIT(x, 0, win.tw - 1);
+ return x / win.cw;
+ }
+@@ -339,7 +340,7 @@ evcol(XEvent *e)
+ int
+ evrow(XEvent *e)
+ {
+- int y = e->xbutton.y - borderpx;
++ int y = e->xbutton.y - win.vborderpx;
+ LIMIT(y, 0, win.th - 1);
+ return y / win.ch;
+ }
+@@ -739,6 +740,9 @@ cresize(int width, int height)
+ col = MAX(1, col);
+ row = MAX(1, row);
+
++ win.hborderpx = (win.w - col * win.cw) / 2;
++ win.vborderpx = (win.h - row * win.ch) / 2;
++
+ tresize(col, row);
+ xresize(col, row);
+ ttyresize(win.tw, win.th);
+@@ -869,8 +873,8 @@ xhints(void)
+ sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
+ sizeh->height = win.h;
+ sizeh->width = win.w;
+- sizeh->height_inc = win.ch;
+- sizeh->width_inc = win.cw;
++ sizeh->height_inc = 1;
++ sizeh->width_inc = 1;
+ sizeh->base_height = 2 * borderpx;
+ sizeh->base_width = 2 * borderpx;
+ sizeh->min_height = win.ch + 2 * borderpx;
+@@ -1152,8 +1156,8 @@ xinit(int cols, int rows)
+ xloadcols();
+
+ /* adjust fixed window geometry */
+- win.w = 2 * borderpx + cols * win.cw;
+- win.h = 2 * borderpx + rows * win.ch;
++ win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
++ win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
+ if (xw.gm & XNegative)
+ xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
+ if (xw.gm & YNegative)
+@@ -1242,7 +1246,7 @@ xinit(int cols, int rows)
+ int
+ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
+ {
+- float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
++ float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp;
+ ushort mode, prevmode = USHRT_MAX;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+@@ -1375,7 +1379,7 @@ void
+ 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,
++ int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch,
+ width = charlen * win.cw;
+ Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
+ XRenderColor colfg, colbg;
+@@ -1465,17 +1469,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+
+ /* Intelligent cleaning up of the borders. */
+ if (x == 0) {
+- xclear(0, (y == 0)? 0 : winy, borderpx,
++ xclear(0, (y == 0)? 0 : winy, win.hborderpx,
+ winy + win.ch +
+- ((winy + win.ch >= borderpx + win.th)? win.h : 0));
++ ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
+ }
+- if (winx + width >= borderpx + win.tw) {
++ if (winx + width >= win.hborderpx + win.tw) {
+ xclear(winx + width, (y == 0)? 0 : winy, win.w,
+- ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
++ ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
+ }
+ if (y == 0)
+- xclear(winx, 0, winx + width, borderpx);
+- if (winy + win.ch >= borderpx + win.th)
++ xclear(winx, 0, winx + width, win.vborderpx);
++ if (winy + win.ch >= win.vborderpx + win.th)
+ xclear(winx, winy + win.ch, winx + width, win.h);
+
+ /* Clean up the region we want to draw to. */
+@@ -1569,35 +1573,35 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+ case 3: /* Blinking Underline */
+ case 4: /* Steady Underline */
+ XftDrawRect(xw.draw, &drawcol,
+- borderpx + cx * win.cw,
+- borderpx + (cy + 1) * win.ch - \
++ win.hborderpx + cx * win.cw,
++ win.vborderpx + (cy + 1) * win.ch - \
+ cursorthickness,
+ win.cw, cursorthickness);
+ break;
+ case 5: /* Blinking bar */
+ case 6: /* Steady bar */
+ XftDrawRect(xw.draw, &drawcol,
+- borderpx + cx * win.cw,
+- borderpx + cy * win.ch,
++ win.hborderpx + cx * win.cw,
++ win.vborderpx + cy * win.ch,
+ cursorthickness, win.ch);
+ break;
+ }
+ } else {
+ XftDrawRect(xw.draw, &drawcol,
+- borderpx + cx * win.cw,
+- borderpx + cy * win.ch,
++ win.hborderpx + cx * win.cw,
++ win.vborderpx + cy * win.ch,
+ win.cw - 1, 1);
+ XftDrawRect(xw.draw, &drawcol,
+- borderpx + cx * win.cw,
+- borderpx + cy * win.ch,
++ win.hborderpx + cx * win.cw,
++ win.vborderpx + cy * win.ch,
+ 1, win.ch - 1);
+ XftDrawRect(xw.draw, &drawcol,
+- borderpx + (cx + 1) * win.cw - 1,
+- borderpx + cy * win.ch,
++ win.hborderpx + (cx + 1) * win.cw - 1,
++ win.vborderpx + cy * win.ch,
+ 1, win.ch - 1);
+ XftDrawRect(xw.draw, &drawcol,
+- borderpx + cx * win.cw,
+- borderpx + (cy + 1) * win.ch - 1,
++ win.hborderpx + cx * win.cw,
++ win.vborderpx + (cy + 1) * win.ch - 1,
+ win.cw, 1);
+ }
+ }
+--
+2.37.1
+
diff --git a/patches/st-expected-anysize-0.9.diff b/patches/st-expected-anysize-0.9.diff
new file mode 100644
index 0000000..e3479de
--- /dev/null
+++ b/patches/st-expected-anysize-0.9.diff
@@ -0,0 +1,15 @@
+diff --git a/x.c b/x.c
+index aa09997..ea6e016 100644
+--- a/x.c
++++ b/x.c
+@@ -869,8 +869,8 @@ xhints(void)
+ sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
+ sizeh->height = win.h;
+ sizeh->width = win.w;
+- sizeh->height_inc = win.ch;
+- sizeh->width_inc = win.cw;
++ sizeh->height_inc = 1;
++ sizeh->width_inc = 1;
+ sizeh->base_height = 2 * borderpx;
+ sizeh->base_width = 2 * borderpx;
+ sizeh->min_height = win.ch + 2 * borderpx;
diff --git a/sixel.c b/sixel.c
new file mode 100644
index 0000000..fef2442
--- /dev/null
+++ b/sixel.c
@@ -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;
+}
diff --git a/sixel.h b/sixel.h
new file mode 100644
index 0000000..7d14f8a
--- /dev/null
+++ b/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/sixel_hls.c b/sixel_hls.c
new file mode 100644
index 0000000..c88241c
--- /dev/null
+++ b/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/sixel_hls.h b/sixel_hls.h
new file mode 100644
index 0000000..6176589
--- /dev/null
+++ b/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/st.c b/st.c
index 8e57991..269d553 100644
--- a/st.c
+++ b/st.c
@@ -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 @@ 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 {
- Glyph attr; /* current char attributes */
- int x;
- int y;
- char state;
-} TCursor;
-
-typedef struct {
int mode;
int type;
int snap;
@@ -109,27 +111,6 @@ typedef struct {
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 @@ typedef struct {
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 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);
@@ -174,7 +157,9 @@ static void tdumpline(int);
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 tsetattr(const int *, int);
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 char base64dec_getc(const char **);
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 @@ char *
xstrdup(const char *s)
{
char *p;
-
if ((p = strdup(s)) == NULL)
die("strdup: %s\n", strerror(errno));
@@ -283,24 +265,27 @@ xstrdup(const char *s)
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 @@ selextend(int col, int row, int type, int done)
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 @@ selnormalize(void)
/* 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 @@ selsnap(int *x, int *y, int direction)
*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 @@ getsel(void)
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 @@ getsel(void)
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 @@ getsel(void)
* 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 @@ selclear(void)
{
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 @@ execsh(char *cmd, char **args)
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 @@ sigchld(int a)
int stat;
pid_t p;
- if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
- die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
-
- if (pid != p)
- return;
+ while ((p = waitpid(-1, &stat, WNOHANG)) > 0) {
+ if (p == pid) {
- 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 @@ int
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 @@ ttynew(const char *line, char *cmd, const char *out, char **args)
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 @@ ttynew(const char *line, char *cmd, const char *out, char **args)
#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 @@ tattrset(int attr)
return 0;
}
+int
+tisaltscr(void)
+{
+ return IS_SET(MODE_ALTSCREEN);
+}
+
void
tsetdirt(int top, int bot)
{
@@ -988,6 +992,13 @@ tsetdirtattr(int attr)
}
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 @@ tcursor(int mode)
}
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 @@ treset(void)
tmoveto(0, 0);
tcursor(CURSOR_SAVE);
tclearregion(0, 0, term.col-1, term.row-1);
+ tdeleteimages();
tswapscreen();
}
}
@@ -1047,9 +1062,12 @@ void
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 @@ tswapscreen(void)
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 @@ tscrolldown(int orig, 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);
+ }
+ }
+
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 @@ tscrollup(int orig, 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 < itop)
+ delete_image(im);
+ }
+ }
+
selscroll(orig, -n);
}
@@ -1218,6 +1266,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
term.dirty[y] = 1;
term.line[y][x] = *attr;
term.line[y][x].u = u;
+
}
void
@@ -1292,10 +1341,22 @@ tinsertblankline(int n)
}
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
@@ -1475,7 +1536,8 @@ tsetscroll(int t, int b)
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) {
@@ -1546,8 +1608,7 @@ tsetmode(int priv, int set, const int *args, int narg)
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();
@@ -1570,6 +1631,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",
@@ -1605,8 +1672,11 @@ tsetmode(int priv, int set, const int *args, int narg)
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:
@@ -1704,19 +1774,30 @@ csihandle(void)
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;
@@ -1725,19 +1806,43 @@ csihandle(void)
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;
@@ -1785,9 +1890,9 @@ csihandle(void)
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;
@@ -1806,6 +1911,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) */
if (csiescseq.priv) {
goto unknown;
@@ -1870,8 +1996,8 @@ osc_color_response(int num, int index, int is_osc4)
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",
@@ -1891,6 +2017,11 @@ strhandle(void)
{ 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();
@@ -1924,6 +2055,8 @@ strhandle(void)
}
}
return;
+ case 8: /* Clear Hyperlinks */
+ return;
case 10: /* set dynamic VT100 text foreground color */
case 11: /* set dynamic VT100 text background color */
case 12: /* set dynamic text cursor color */
@@ -1986,6 +2119,95 @@ 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;
+ /* 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;
@@ -2007,6 +2229,16 @@ strparse(void)
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')
@@ -2041,7 +2273,7 @@ strdump(void)
fprintf(stderr, "(%02x)", c);
}
}
- fprintf(stderr, "ESC\\\n");
+ fprintf(stderr, (strescseq.term[0] == 0x1b) ? "ESC\\\n" : "BEL\n");
}
void
@@ -2179,9 +2411,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 = '_';
@@ -2193,7 +2428,6 @@ tstrsequence(uchar c)
c = ']';
break;
}
- strreset();
strescseq.type = c;
term.esc |= ESC_STR;
}
@@ -2220,6 +2454,7 @@ tcontrolcode(uchar ascii)
case '\a': /* BEL */
if (term.esc & ESC_STR_END) {
/* backwards compatibility to xterm */
+ strescseq.term = STR_TERM_BEL;
strhandle();
} else {
xbell();
@@ -2295,6 +2530,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
@@ -2313,6 +2580,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 */
@@ -2372,8 +2640,10 @@ eschandle(uchar ascii)
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",
@@ -2392,7 +2662,8 @@ tputc(Rune u)
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 {
@@ -2413,11 +2684,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
@@ -2471,6 +2745,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) {
@@ -2489,6 +2772,7 @@ check_control_code:
*/
return;
}
+
if (selected(term.c.x, term.c.y))
selclear();
@@ -2541,7 +2825,11 @@ 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);
if (charsize == 0)
@@ -2568,11 +2856,13 @@ twrite(const char *buf, int buflen, int show_ctrl)
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,
@@ -2580,23 +2870,19 @@ tresize(int col, int row)
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 */
@@ -2616,25 +2902,27 @@ tresize(int col, int row)
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);
}
@@ -2644,7 +2932,22 @@ tresize(int col, int row)
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
@@ -2667,6 +2970,7 @@ drawregion(int x1, int y1, int x2, int y2)
}
}
+
void
draw(void)
{
@@ -2684,6 +2988,7 @@ draw(void)
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;
diff --git a/st.h b/st.h
index fd3b0d8..0d04bd8 100644
--- a/st.h
+++ b/st.h
@@ -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 unsigned short ushort;
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 @@ typedef union {
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 sendbreak(const Arg *);
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 resettitle(void);
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 *xmalloc(size_t);
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 tabspaces;
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;
diff --git a/x.c b/x.c
index d73152b..0366726 100644
--- a/x.c
+++ b/x.c
@@ -20,30 +20,8 @@ char *argv0;
#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 clipcopy(const Arg *);
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 @@ static void ttysend(const Arg *);
#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 cmessage(XEvent *);
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 selnotify(XEvent *);
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 @@ static void (*handler[LASTEvent])(XEvent *) = {
};
/* 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 char *opt_title = NULL;
static uint buttons; /* bit field of pressed buttons */
+
void
clipcopy(const Arg *dummy)
{
@@ -280,16 +194,23 @@ clippaste(const Arg *dummy)
}
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 @@ 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)
{
+ 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 @@ zoomreset(const Arg *arg)
}
}
-void
-ttysend(const Arg *arg)
-{
- ttywrite(arg->s, strlen(arg->s), 1);
-}
-
int
evcol(XEvent *e)
{
@@ -344,6 +276,40 @@ evrow(XEvent *e)
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 @@ mousereport(XEvent *e)
/* 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 @@ mousereport(XEvent *e)
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 @@ bpress(XEvent *e)
xsel.tclick1 = now;
selstart(evcol(e), evrow(e), snap);
+
}
}
@@ -515,6 +451,7 @@ propnotify(XEvent *e)
xpev->atom == clipboard)) {
selnotify(e);
}
+
}
void
@@ -545,7 +482,8 @@ selnotify(XEvent *e)
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 @@ setsel(char *str, Time t)
XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
selclear();
+
}
void
@@ -709,13 +648,17 @@ brelease(XEvent *e)
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 @@ cresize(int width, int height)
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 @@ xresize(int col, int row)
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 @@ xclear(int x1, int y1, int x2, int y2)
}
void
+xclearwin(void)
+{
+ xclear(0, 0, win.w, win.h);
+}
+
+void
xhints(void)
{
XClassHint class = {opt_name ? opt_name : termname,
@@ -908,6 +858,60 @@ xgeommasktogravity(int mask)
}
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 @@ xunloadfont(Font *f)
void
xunloadfonts(void)
{
+
/* Free the loaded fonts in the font cache. */
while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font);
@@ -1072,60 +1077,6 @@ xunloadfonts(void)
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 @@ xinit(int cols, int rows)
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 @@ xinit(int cols, int rows)
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 @@ xinit(int cols, int rows)
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 @@ xinit(int cols, int rows)
/* input methods */
if (!ximopen(xw.dpy)) {
XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
- ximinstantiate, NULL);
+ ximinstantiate, NULL);
}
/* white cursor, black outline */
@@ -1240,6 +1194,7 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
+
}
int
@@ -1249,7 +1204,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
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 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
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 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
/* 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 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
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 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
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 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
}
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 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
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 @@ void
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 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
/* 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 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
/*
* 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 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
g.fg = defaultbg;
g.bg = defaultcs;
}
+
drawcol = dc.col[g.bg];
}
@@ -1564,13 +1520,13 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
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 @@ xseticontitle(char *p)
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 @@ xsettitle(char *p)
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 @@ xdrawline(Line line, int x1, int y1, int x2)
{
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 @@ xdrawline(Line line, int x1, int y1, int x2)
}
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 @@ kpress(XEvent *ev)
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 @@ kpress(XEvent *ev)
} 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 @@ cmessage(XEvent *e)
void
resize(XEvent *e)
{
+
if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
return;
@@ -1992,7 +2070,8 @@ run(void)
* 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 @@ run(void)
/* 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 @@ run(void)
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 @@ run:
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
+
cols = MAX(cols, 1);
rows = MAX(rows, 1);
tnew(cols, rows);