From f69f7f97dff9ef63ac1d85def1b69e70b8dc52eb Mon Sep 17 00:00:00 2001 From: Quentin Snow Date: Sat, 27 Feb 2021 03:28:41 -0600 Subject: [PATCH] Added sixel support --- Makefile | 2 +- config.mk | 2 +- config.mk.orig | 35 + sixel-new.diff | 1373 +++++++++++++++++++++++++ sixel.c | 616 ++++++++++++ sixel.h | 58 ++ sixel.o | Bin 0 -> 6552 bytes sixel_hls.c | 115 +++ sixel_hls.h | 7 + sixel_hls.o | Bin 0 -> 2872 bytes st | Bin 101968 -> 110848 bytes st.c | 138 ++- st.c.orig | 2605 ++++++++++++++++++++++++++++++++++++++++++++++++ st.c.rej | 119 +++ st.h | 54 + st.h.orig | 125 +++ st.o | Bin 73752 -> 75112 bytes x.c | 110 +- x.c.orig | 2065 ++++++++++++++++++++++++++++++++++++++ x.o | Bin 74680 -> 77608 bytes 20 files changed, 7382 insertions(+), 42 deletions(-) create mode 100644 config.mk.orig create mode 100644 sixel-new.diff create mode 100644 sixel.c create mode 100644 sixel.h create mode 100644 sixel.o create mode 100644 sixel_hls.c create mode 100644 sixel_hls.h create mode 100644 sixel_hls.o create mode 100644 st.c.orig create mode 100644 st.c.rej create mode 100644 st.h.orig create mode 100644 x.c.orig diff --git a/Makefile b/Makefile index 470ac86..0bc8774 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c +SRC = st.c x.c sixel.c sixel_hls.c OBJ = $(SRC:.c=.o) all: options st diff --git a/config.mk b/config.mk index c070a4a..780f0df 100644 --- a/config.mk +++ b/config.mk @@ -32,4 +32,4 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) # `$(PKG_CONFIG) --libs freetype2` # compiler and linker -# CC = c99 +CC = c99 diff --git a/config.mk.orig b/config.mk.orig new file mode 100644 index 0000000..c070a4a --- /dev/null +++ b/config.mk.orig @@ -0,0 +1,35 @@ +# st version +VERSION = 0.8.4 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +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` + +# compiler and linker +# CC = c99 diff --git a/sixel-new.diff b/sixel-new.diff new file mode 100644 index 0000000..a9cdb59 --- /dev/null +++ b/sixel-new.diff @@ -0,0 +1,1373 @@ +diff --git a/Makefile b/Makefile +index 470ac86..0bc8774 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c sixel.c sixel_hls.c + OBJ = $(SRC:.c=.o) + + all: options st +diff --git a/config.mk b/config.mk +index 0cbb002..d7fc850 100644 +--- a/config.mk ++++ b/config.mk +@@ -32,4 +32,4 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) + # `pkg-config --libs freetype2` + + # compiler and linker +-# CC = c99 ++CC = c99 +diff --git a/sixel.c b/sixel.c +new file mode 100644 +index 0000000..7bfe598 +--- /dev/null ++++ b/sixel.c +@@ -0,0 +1,616 @@ ++// sixel.c (part of mintty) ++// originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c) ++// Licensed under the terms of the GNU General Public License v3 or later. ++ ++#include ++#include /* memcpy */ ++ ++#include "sixel.h" ++#include "sixel_hls.h" ++ ++#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) + ((b) << 16)) ++#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% */ ++}; ++ ++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) ++{ ++ free(image->data); ++ image->data = NULL; ++} ++ ++int ++sixel_parser_init(sixel_state_t *st, ++ 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->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, 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, unsigned char *pixels) ++{ ++ int status = (-1); ++ int sx; ++ int sy; ++ sixel_image_t *image = &st->image; ++ int x, y; ++ sixel_color_no_t *src; ++ unsigned char *dst; ++ int color; ++ ++ 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; ++ ++ sx = (st->max_x + st->grid_width - 1) / st->grid_width * st->grid_width; ++ sy = (st->max_y + st->grid_height - 1) / st->grid_height * st->grid_height; ++ ++ if (image->width > sx || image->height > sy) { ++ status = image_buffer_resize(image, sx, sy); ++ if (status < 0) ++ goto end; ++ } ++ ++ if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) { ++ status = set_default_color(image); ++ if (status < 0) ++ goto end; ++ } ++ ++ src = st->image.data; ++ dst = pixels; ++ for (y = 0; y < st->image.height; ++y) { ++ for (x = 0; x < st->image.width; ++x) { ++ color = st->image.palette[*src++]; ++ *dst++ = color >> 16 & 0xff; /* b */ ++ *dst++ = color >> 8 & 0xff; /* g */ ++ *dst++ = color >> 0 & 0xff; /* r */ ++ dst++; /* a */ ++ } ++ /* fill right padding with bgcolor */ ++ for (; x < st->image.width; ++x) { ++ color = st->image.palette[0]; /* bgcolor */ ++ *dst++ = color >> 16 & 0xff; /* b */ ++ *dst++ = color >> 8 & 0xff; /* g */ ++ *dst++ = color >> 0 & 0xff; /* r */ ++ dst++; /* a */ ++ } ++ } ++ /* fill bottom padding with bgcolor */ ++ for (; y < st->image.height; ++y) { ++ for (x = 0; x < st->image.width; ++x) { ++ color = st->image.palette[0]; /* bgcolor */ ++ *dst++ = color >> 16 & 0xff; /* b */ ++ *dst++ = color >> 8 & 0xff; /* g */ ++ *dst++ = color >> 0 & 0xff; /* r */ ++ dst++; /* a */ ++ } ++ } ++ ++ status = (0); ++ ++end: ++ return status; ++} ++ ++/* convert sixel data into indexed pixel bytes and palette data */ ++int ++sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len) ++{ ++ int status = (-1); ++ int n; ++ int i; ++ int x; ++ int y; ++ int bits; ++ int sixel_vertical_mask; ++ int sx; ++ int sy; ++ int c; ++ int pos; ++ unsigned char *p0 = p; ++ sixel_image_t *image = &st->image; ++ ++ if (! image->data) ++ goto end; ++ ++ while (p < p0 + len) { ++ switch (st->state) { ++ case PS_ESC: ++ goto end; ++ ++ case PS_DECSIXEL: ++ switch (*p) { ++ case '\x1b': ++ st->state = PS_ESC; ++ p++; ++ 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; ++ } ++ ++ if (sx > DECSIXEL_WIDTH_MAX) ++ sx = DECSIXEL_WIDTH_MAX; ++ if (sy > DECSIXEL_HEIGHT_MAX) ++ sy = DECSIXEL_HEIGHT_MAX; ++ ++ status = image_buffer_resize(image, sx, sy); ++ if (status < 0) ++ goto end; ++ } ++ ++ 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) { ++ sixel_vertical_mask = 0x01; ++ if (st->repeat_count <= 1) { ++ for (i = 0; i < 6; i++) { ++ if ((bits & sixel_vertical_mask) != 0) { ++ pos = image->width * (st->pos_y + i) + st->pos_x; ++ image->data[pos] = st->color_index; ++ if (st->max_x < st->pos_x) ++ st->max_x = st->pos_x; ++ if (st->max_y < (st->pos_y + i)) ++ st->max_y = st->pos_y + i; ++ } ++ sixel_vertical_mask <<= 1; ++ } ++ } else { ++ /* st->repeat_count > 1 */ ++ for (i = 0; i < 6; i++) { ++ if ((bits & sixel_vertical_mask) != 0) { ++ c = sixel_vertical_mask << 1; ++ for (n = 1; (i + n) < 6; n++) { ++ if ((bits & c) == 0) ++ break; ++ c <<= 1; ++ } ++ for (y = st->pos_y + i; y < st->pos_y + i + n; ++y) { ++ for (x = st->pos_x; x < st->pos_x + st->repeat_count; ++x) ++ image->data[image->width * y + x] = st->color_index; ++ } ++ 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 + i + n - 1)) ++ st->max_y = st->pos_y + i + n - 1; ++ i += (n - 1); ++ sixel_vertical_mask <<= (n - 1); ++ } ++ sixel_vertical_mask <<= 1; ++ } ++ } ++ } ++ } ++ 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; ++ p++; ++ 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'; ++ if (st->param > DECSIXEL_PARAMVALUE_MAX) ++ 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 = st->attributed_ph; ++ if (image->width > st->attributed_ph) ++ sx = image->width; ++ ++ sy = st->attributed_pv; ++ if (image->height > st->attributed_pv) ++ sy = image->height; ++ ++ sx = (sx + st->grid_width - 1) / st->grid_width * st->grid_width; ++ sy = (sy + st->grid_height - 1) / st->grid_height * st->grid_height; ++ ++ if (sx > DECSIXEL_WIDTH_MAX) ++ sx = DECSIXEL_WIDTH_MAX; ++ if (sy > DECSIXEL_HEIGHT_MAX) ++ sy = DECSIXEL_HEIGHT_MAX; ++ ++ status = image_buffer_resize(image, sx, sy); ++ if (status < 0) ++ goto end; ++ } ++ 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; ++ p++; ++ 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'; ++ if (st->param > DECSIXEL_PARAMVALUE_MAX) ++ st->param = DECSIXEL_PARAMVALUE_MAX; ++ p++; ++ break; ++ default: ++ st->repeat_count = st->param; ++ if (st->repeat_count == 0) ++ st->repeat_count = 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; ++ p++; ++ 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'; ++ if (st->param > DECSIXEL_PARAMVALUE_MAX) ++ 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 */ ++ if (st->params[2] > 360) ++ st->params[2] = 360; ++ if (st->params[3] > 100) ++ st->params[3] = 100; ++ if (st->params[4] > 100) ++ 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 */ ++ if (st->params[2] > 100) ++ st->params[2] = 100; ++ if (st->params[3] > 100) ++ st->params[3] = 100; ++ if (st->params[4] > 100) ++ st->params[4] = 100; ++ image->palette[st->color_index] ++ = SIXEL_XRGB(st->params[2], st->params[3], st->params[4]); ++ } ++ } ++ break; ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ status = (0); ++ ++end: ++ return status; ++} ++ ++void ++sixel_parser_deinit(sixel_state_t *st) ++{ ++ if (st) ++ sixel_image_deinit(&st->image); ++} +diff --git a/sixel.h b/sixel.h +new file mode 100644 +index 0000000..8a05c44 +--- /dev/null ++++ b/sixel.h +@@ -0,0 +1,58 @@ ++#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 */ ++} 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 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; ++ ++int sixel_parser_init(sixel_state_t *st, 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, unsigned char *p, size_t len); ++int sixel_parser_set_default_color(sixel_state_t *st); ++int sixel_parser_finalize(sixel_state_t *st, unsigned char *pixels); ++void sixel_parser_deinit(sixel_state_t *st); ++ ++#endif +diff --git a/sixel.o b/sixel.o +new file mode 100644 +index 0000000..7d76b3c +diff --git a/sixel_hls.c b/sixel_hls.c +new file mode 100644 +index 0000000..4f157b2 +--- /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) << 16) + ((g) << 8) + (b)) ++ ++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/sixel_hls.o b/sixel_hls.o +new file mode 100644 +index 0000000..92d59ab +diff --git a/st.c b/st.c +index 8e6ccb5..4001a1c 100644 +--- a/st.c ++++ b/st.c +@@ -19,6 +19,7 @@ + + #include "st.h" + #include "win.h" ++#include "sixel.h" + + #if defined(__linux) + #include +@@ -86,13 +87,6 @@ enum escape_state { + ESC_DCS =128, + }; + +-typedef struct { +- Glyph attr; /* current char attributes */ +- int x; +- int y; +- char state; +-} TCursor; +- + typedef struct { + int mode; + int type; +@@ -111,26 +105,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; +-} Term; +- + /* CSI Escape sequence structs */ + /* ESC '[' [[ [] [;]] []] */ + typedef struct { +@@ -158,6 +132,7 @@ static void sigchld(int); + static void ttywriteraw(const char *, size_t); + + static void csidump(void); ++static void dcshandle(void); + static void csihandle(void); + static void csiparse(void); + static void csireset(void); +@@ -218,13 +193,14 @@ static char base64dec_getc(const char **); + static ssize_t xwrite(int, const char *, size_t); + + /* Globals */ +-static Term term; ++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 uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; + static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +@@ -998,6 +974,7 @@ void + treset(void) + { + uint i; ++ ImageList *im; + + term.c = (TCursor){{ + .mode = ATTR_NULL, +@@ -1020,6 +997,9 @@ treset(void) + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } ++ ++ for (im = term.images; im; im = im->next) ++ im->should_delete = 1; + } + + void +@@ -1034,9 +1014,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(); + } +@@ -1046,6 +1029,7 @@ tscrolldown(int orig, int n) + { + int i; + Line temp; ++ ImageList *im; + + LIMIT(n, 0, term.bot-orig+1); + +@@ -1058,6 +1042,13 @@ tscrolldown(int orig, int n) + term.line[i-n] = temp; + } + ++ for (im = term.images; im; im = im->next) { ++ if (im->y < term.bot) ++ im->y += n; ++ if (im->y > term.bot) ++ im->should_delete = 1; ++ } ++ + selscroll(orig, n); + } + +@@ -1066,6 +1057,7 @@ tscrollup(int orig, int n) + { + int i; + Line temp; ++ ImageList *im; + + LIMIT(n, 0, term.bot-orig+1); + +@@ -1078,6 +1070,13 @@ tscrollup(int orig, int n) + term.line[i+n] = temp; + } + ++ for (im = term.images; im; im = im->next) { ++ if (im->y+im->height/win.ch > term.top) ++ im->y -= n; ++ if (im->y+im->height/win.ch < term.top) ++ im->should_delete = 1; ++ } ++ + selscroll(orig, -n); + } + +@@ -1219,6 +1218,7 @@ tclearregion(int x1, int y1, int x2, int y2) + { + int x, y, temp; + Glyph *gp; ++ ImageList *im; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; +@@ -1590,11 +1590,29 @@ tsetmode(int priv, int set, int *args, int narg) + } + } + ++void ++dcshandle(void) ++{ ++ switch (csiescseq.mode[0]) { ++ default: ++ fprintf(stderr, "erresc: unknown csi "); ++ csidump(); ++ /* die(""); */ ++ break; ++ case 'q': /* DECSIXEL */ ++ if (sixel_parser_init(&sixel_st, 0, 0 << 16 | 0 << 8 | 0, 1, win.cw, win.ch) != 0) ++ perror("sixel_parser_init() failed"); ++ term.mode |= MODE_SIXEL; ++ break; ++ } ++} ++ + void + csihandle(void) + { + char buf[40]; + int len; ++ ImageList *im; + + switch (csiescseq.mode[0]) { + default: +@@ -1684,6 +1702,13 @@ csihandle(void) + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ ++ /* purge sixels */ ++ /* TODO: kinda gross, should probably make this only purge ++ * visible sixels ++ */ ++ for (im = term.images; im; im = im->next) ++ im->should_delete = 1; ++ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); +@@ -1831,6 +1856,8 @@ strhandle(void) + { + char *p = NULL, *dec; + int j, narg, par; ++ ImageList *new_image; ++ int i; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); +@@ -1882,7 +1909,39 @@ strhandle(void) + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ +- term.mode |= ESC_DCS; ++ if (IS_SET(MODE_SIXEL)) { ++ term.mode &= ~MODE_SIXEL; ++ new_image = malloc(sizeof(ImageList)); ++ memset(new_image, 0, sizeof(ImageList)); ++ new_image->x = term.c.x; ++ new_image->y = term.c.y; ++ new_image->width = sixel_st.image.width; ++ new_image->height = sixel_st.image.height; ++ new_image->pixels = malloc(new_image->width * new_image->height * 4); ++ if (sixel_parser_finalize(&sixel_st, new_image->pixels) != 0) { ++ perror("sixel_parser_finalize() failed"); ++ sixel_parser_deinit(&sixel_st); ++ return; ++ } ++ sixel_parser_deinit(&sixel_st); ++ if (term.images) { ++ ImageList *im; ++ for (im = term.images; im->next;) ++ im = im->next; ++ im->next = new_image; ++ new_image->prev = im; ++ } else { ++ term.images = new_image; ++ } ++ for (i = 0; i < (sixel_st.image.height + win.ch-1)/win.ch; ++i) { ++ int x; ++ tclearregion(term.c.x, term.c.y, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw, term.c.y); ++ for (x = term.c.x; x < MIN(term.col, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw); x++) ++ term.line[term.c.y][x].mode |= ATTR_SIXEL; ++ tnewline(1); ++ } ++ } ++ return; + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; +@@ -2208,6 +2267,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 */ +@@ -2310,21 +2370,17 @@ tputc(Rune u) + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); +- if (IS_SET(MODE_SIXEL)) { +- /* TODO: render sixel */; +- term.mode &= ~MODE_SIXEL; +- return; +- } + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (IS_SET(MODE_SIXEL)) { +- /* TODO: implement sixel mode */ ++ if (sixel_parser_parse(&sixel_st, (unsigned char *)&u, 1) != 0) ++ perror("sixel_parser_parse() failed"); + return; + } +- if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') +- term.mode |= MODE_SIXEL; ++ if (term.esc & ESC_DCS) ++ goto check_control_code; + + if (strescseq.len+len >= sizeof(strescseq.buf)-1) { + /* +@@ -2371,7 +2427,16 @@ check_control_code: + csihandle(); + } + return; +- } else if (term.esc & ESC_UTF8) { ++ } 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) { + tdeftran(u); +diff --git a/st.h b/st.h +index 4da3051..1444687 100644 +--- a/st.h ++++ b/st.h +@@ -33,6 +33,7 @@ enum glyph_attribute { + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, ++ ATTR_SIXEL = 1 << 11, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +@@ -76,6 +77,56 @@ typedef union { + const void *v; + } Arg; + ++typedef struct { ++ Glyph attr; /* current char attributes */ ++ int x; ++ int y; ++ char state; ++} TCursor; ++ ++typedef struct _ImageList { ++ struct _ImageList *next, *prev; ++ unsigned char *pixels; ++ void *pixmap; ++ int width; ++ int height; ++ int x; ++ int y; ++ int should_delete; ++} ImageList; ++ ++/* 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 */ ++} Term; ++ ++/* 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; ++ + void die(const char *, ...); + void redraw(void); + void draw(void); +@@ -120,3 +171,5 @@ extern char *termname; + extern unsigned int tabspaces; + extern unsigned int defaultfg; + extern unsigned int defaultbg; ++extern Term term; ++extern TermWindow win; +diff --git a/x.c b/x.c +index 5828a3b..efa5d03 100644 +--- a/x.c ++++ b/x.c +@@ -74,16 +74,6 @@ 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; +@@ -209,7 +199,7 @@ static void (*handler[LASTEvent])(XEvent *) = { + static DC dc; + static XWindow xw; + static XSelection xsel; +-static TermWindow win; ++TermWindow win; + + /* Font Ring Cache */ + enum { +@@ -1566,14 +1556,112 @@ xdrawline(Line line, int x1, int y1, int x2) + xdrawglyphfontspecs(specs, base, i, ox, y1); + } + ++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); ++ free(im->pixels); ++ free(im); ++} ++ + void + xfinishdraw(void) + { ++ ImageList *im; ++ int x, y; ++ int n = 0; ++ int nlimit = 256; ++ XRectangle *rects = NULL; ++ XGCValues gcvalues; ++ GC gc; ++ ++ for (im = term.images; im; im = im->next) { ++ if (im->should_delete) { ++ delete_image(im); ++ ++ /* ++ * prevent the next iteration from ++ * accessing an invalid pointer ++ */ ++ im = term.images; ++ if (im == NULL) ++ break; ++ else ++ continue; ++ } ++ if (!im->pixmap) { ++ im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, im->width, im->height, DefaultDepth(xw.dpy, xw.scr)); ++ XImage ximage = { ++ .format = ZPixmap, ++ .data = (char *)im->pixels, ++ .width = im->width, ++ .height = im->height, ++ .xoffset = 0, ++ .byte_order = LSBFirst, ++ .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, im->width, im->height); ++ free(im->pixels); ++ im->pixels = NULL; ++ } ++ n = 0; ++ memset(&gcvalues, 0, sizeof(gcvalues)); ++ gc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues); ++ for (y = im->y; y < im->y + (im->height+win.ch-1)/win.ch; y++) { ++ if (y >= 0 && y < term.row) { ++ for (x = im->x; x < im->x + (im->width+win.cw-1)/win.cw; x++) { ++ if (!rects) ++ rects = xmalloc(sizeof(XRectangle) * nlimit); ++ if (term.line[y][x].mode & ATTR_SIXEL) { ++ if (n > 0 && rects[n-1].x+rects[n-1].width == borderpx+x*win.cw && rects[n-1].y == borderpx+y*win.ch) { ++ rects[n-1].width += win.cw; ++ } else { ++ rects[n].x = borderpx+x*win.cw; ++ rects[n].y = borderpx+y*win.ch; ++ rects[n].width = win.cw; ++ rects[n].height = win.ch; ++ if (++n == nlimit && (rects = realloc(rects, sizeof(XRectangle) * (nlimit *= 2))) == NULL) ++ die("Out of memory\n"); ++ } ++ } ++ } ++ } ++ if (n > 1 && rects[n-2].x == rects[n-1].x && rects[n-2].width == rects[n-1].width) { ++ if (rects[n-2].y+rects[n-2].height == rects[n-1].y) { ++ rects[n-2].height += win.ch; ++ n--; ++ } ++ } ++ } ++ if (n == 0) { ++ delete_image(im); ++ continue; ++ } ++ if (n > 1) ++ XSetClipRectangles(xw.dpy, gc, 0, 0, rects, n, YXSorted); ++ XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, im->width, im->height, borderpx + im->x * win.cw, borderpx + im->y * win.ch); ++ 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); ++ ++ free(rects); + } + + void diff --git a/sixel.c b/sixel.c new file mode 100644 index 0000000..7bfe598 --- /dev/null +++ b/sixel.c @@ -0,0 +1,616 @@ +// sixel.c (part of mintty) +// originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c) +// Licensed under the terms of the GNU General Public License v3 or later. + +#include +#include /* memcpy */ + +#include "sixel.h" +#include "sixel_hls.h" + +#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) + ((b) << 16)) +#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% */ +}; + +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) +{ + free(image->data); + image->data = NULL; +} + +int +sixel_parser_init(sixel_state_t *st, + 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->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, 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, unsigned char *pixels) +{ + int status = (-1); + int sx; + int sy; + sixel_image_t *image = &st->image; + int x, y; + sixel_color_no_t *src; + unsigned char *dst; + int color; + + 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; + + sx = (st->max_x + st->grid_width - 1) / st->grid_width * st->grid_width; + sy = (st->max_y + st->grid_height - 1) / st->grid_height * st->grid_height; + + if (image->width > sx || image->height > sy) { + status = image_buffer_resize(image, sx, sy); + if (status < 0) + goto end; + } + + if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) { + status = set_default_color(image); + if (status < 0) + goto end; + } + + src = st->image.data; + dst = pixels; + for (y = 0; y < st->image.height; ++y) { + for (x = 0; x < st->image.width; ++x) { + color = st->image.palette[*src++]; + *dst++ = color >> 16 & 0xff; /* b */ + *dst++ = color >> 8 & 0xff; /* g */ + *dst++ = color >> 0 & 0xff; /* r */ + dst++; /* a */ + } + /* fill right padding with bgcolor */ + for (; x < st->image.width; ++x) { + color = st->image.palette[0]; /* bgcolor */ + *dst++ = color >> 16 & 0xff; /* b */ + *dst++ = color >> 8 & 0xff; /* g */ + *dst++ = color >> 0 & 0xff; /* r */ + dst++; /* a */ + } + } + /* fill bottom padding with bgcolor */ + for (; y < st->image.height; ++y) { + for (x = 0; x < st->image.width; ++x) { + color = st->image.palette[0]; /* bgcolor */ + *dst++ = color >> 16 & 0xff; /* b */ + *dst++ = color >> 8 & 0xff; /* g */ + *dst++ = color >> 0 & 0xff; /* r */ + dst++; /* a */ + } + } + + status = (0); + +end: + return status; +} + +/* convert sixel data into indexed pixel bytes and palette data */ +int +sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len) +{ + int status = (-1); + int n; + int i; + int x; + int y; + int bits; + int sixel_vertical_mask; + int sx; + int sy; + int c; + int pos; + unsigned char *p0 = p; + sixel_image_t *image = &st->image; + + if (! image->data) + goto end; + + while (p < p0 + len) { + switch (st->state) { + case PS_ESC: + goto end; + + case PS_DECSIXEL: + switch (*p) { + case '\x1b': + st->state = PS_ESC; + p++; + 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; + } + + if (sx > DECSIXEL_WIDTH_MAX) + sx = DECSIXEL_WIDTH_MAX; + if (sy > DECSIXEL_HEIGHT_MAX) + sy = DECSIXEL_HEIGHT_MAX; + + status = image_buffer_resize(image, sx, sy); + if (status < 0) + goto end; + } + + 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) { + sixel_vertical_mask = 0x01; + if (st->repeat_count <= 1) { + for (i = 0; i < 6; i++) { + if ((bits & sixel_vertical_mask) != 0) { + pos = image->width * (st->pos_y + i) + st->pos_x; + image->data[pos] = st->color_index; + if (st->max_x < st->pos_x) + st->max_x = st->pos_x; + if (st->max_y < (st->pos_y + i)) + st->max_y = st->pos_y + i; + } + sixel_vertical_mask <<= 1; + } + } else { + /* st->repeat_count > 1 */ + for (i = 0; i < 6; i++) { + if ((bits & sixel_vertical_mask) != 0) { + c = sixel_vertical_mask << 1; + for (n = 1; (i + n) < 6; n++) { + if ((bits & c) == 0) + break; + c <<= 1; + } + for (y = st->pos_y + i; y < st->pos_y + i + n; ++y) { + for (x = st->pos_x; x < st->pos_x + st->repeat_count; ++x) + image->data[image->width * y + x] = st->color_index; + } + 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 + i + n - 1)) + st->max_y = st->pos_y + i + n - 1; + i += (n - 1); + sixel_vertical_mask <<= (n - 1); + } + sixel_vertical_mask <<= 1; + } + } + } + } + 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; + p++; + 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'; + if (st->param > DECSIXEL_PARAMVALUE_MAX) + 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 = st->attributed_ph; + if (image->width > st->attributed_ph) + sx = image->width; + + sy = st->attributed_pv; + if (image->height > st->attributed_pv) + sy = image->height; + + sx = (sx + st->grid_width - 1) / st->grid_width * st->grid_width; + sy = (sy + st->grid_height - 1) / st->grid_height * st->grid_height; + + if (sx > DECSIXEL_WIDTH_MAX) + sx = DECSIXEL_WIDTH_MAX; + if (sy > DECSIXEL_HEIGHT_MAX) + sy = DECSIXEL_HEIGHT_MAX; + + status = image_buffer_resize(image, sx, sy); + if (status < 0) + goto end; + } + 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; + p++; + 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'; + if (st->param > DECSIXEL_PARAMVALUE_MAX) + st->param = DECSIXEL_PARAMVALUE_MAX; + p++; + break; + default: + st->repeat_count = st->param; + if (st->repeat_count == 0) + st->repeat_count = 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; + p++; + 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'; + if (st->param > DECSIXEL_PARAMVALUE_MAX) + 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 */ + if (st->params[2] > 360) + st->params[2] = 360; + if (st->params[3] > 100) + st->params[3] = 100; + if (st->params[4] > 100) + 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 */ + if (st->params[2] > 100) + st->params[2] = 100; + if (st->params[3] > 100) + st->params[3] = 100; + if (st->params[4] > 100) + st->params[4] = 100; + image->palette[st->color_index] + = SIXEL_XRGB(st->params[2], st->params[3], st->params[4]); + } + } + break; + } + break; + default: + break; + } + } + + status = (0); + +end: + return status; +} + +void +sixel_parser_deinit(sixel_state_t *st) +{ + if (st) + sixel_image_deinit(&st->image); +} diff --git a/sixel.h b/sixel.h new file mode 100644 index 0000000..8a05c44 --- /dev/null +++ b/sixel.h @@ -0,0 +1,58 @@ +#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 */ +} 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 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; + +int sixel_parser_init(sixel_state_t *st, 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, unsigned char *p, size_t len); +int sixel_parser_set_default_color(sixel_state_t *st); +int sixel_parser_finalize(sixel_state_t *st, unsigned char *pixels); +void sixel_parser_deinit(sixel_state_t *st); + +#endif diff --git a/sixel.o b/sixel.o new file mode 100644 index 0000000000000000000000000000000000000000..1cf3e5a969aef577c13776a21b804f3d8f980a5f GIT binary patch literal 6552 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 RZ0ld){?{k1MKN{#KLO$Fo+$tT literal 0 HcmV?d00001 diff --git a/sixel_hls.c b/sixel_hls.c new file mode 100644 index 0000000..4f157b2 --- /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) << 16) + ((g) << 8) + (b)) + +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/sixel_hls.o b/sixel_hls.o new file mode 100644 index 0000000000000000000000000000000000000000..ed26ed3d9f11e2d56844c05de235bd0f169bd370 GIT binary patch literal 2872 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

!m@L`t{SG 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

+lf&!t8T2)xB)Syglu8MNI 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 literal 101968 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 @@ -51,6 +52,7 @@ enum term_mode { MODE_ECHO = 1 << 4, MODE_PRINT = 1 << 5, MODE_UTF8 = 1 << 6, + MODE_SIXEL = 1 << 7, }; enum cursor_movement { @@ -84,13 +86,6 @@ enum escape_state { ESC_UTF8 = 64, }; -typedef struct { - Glyph attr; /* current char attributes */ - int x; - int y; - char state; -} TCursor; - typedef struct { int mode; int type; @@ -109,26 +104,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 '[' [[ [] [;]] []] */ @@ -158,6 +133,7 @@ static void sigchld(int); static void ttywriteraw(const char *, size_t); static void csidump(void); +static void dcshandle(void); static void csihandle(void); static void csiparse(void); static void csireset(void); @@ -218,13 +194,14 @@ static char base64dec_getc(const char **); static ssize_t xwrite(int, const char *, size_t); /* Globals */ -static Term term; +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 uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; @@ -1015,6 +992,7 @@ void treset(void) { uint i; + ImageList *im; term.c = (TCursor){{ .mode = ATTR_NULL, @@ -1037,6 +1015,9 @@ treset(void) tclearregion(0, 0, term.col-1, term.row-1); tswapscreen(); } + + for (im = term.images; im; im = im->next) + im->should_delete = 1; } void @@ -1051,9 +1032,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(); } @@ -1063,6 +1047,7 @@ tscrolldown(int orig, int n) { int i; Line temp; + ImageList *im; LIMIT(n, 0, term.bot-orig+1); @@ -1075,6 +1060,13 @@ tscrolldown(int orig, int n) term.line[i-n] = temp; } + for (im = term.images; im; im = im->next) { + if (im->y < term.bot) + im->y += n; + if (im->y > term.bot) + im->should_delete = 1; + } + selscroll(orig, n); } @@ -1083,6 +1075,7 @@ tscrollup(int orig, int n) { int i; Line temp; + ImageList *im; LIMIT(n, 0, term.bot-orig+1); @@ -1095,6 +1088,13 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } + for (im = term.images; im; im = im->next) { + if (im->y+im->height/win.ch > term.top) + im->y -= n; + if (im->y+im->height/win.ch < term.top) + im->should_delete = 1; + } + selscroll(orig, -n); } @@ -1226,6 +1226,7 @@ tclearregion(int x1, int y1, int x2, int y2) { int x, y, temp; Glyph *gp; + ImageList *im; if (x1 > x2) temp = x1, x1 = x2, x2 = temp; @@ -1597,11 +1598,29 @@ tsetmode(int priv, int set, int *args, int narg) } } +void +dcshandle(void) +{ + switch (csiescseq.mode[0]) { + default: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case 'q': /* DECSIXEL */ + if (sixel_parser_init(&sixel_st, 0, 0 << 16 | 0 << 8 | 0, 1, win.cw, win.ch) != 0) + perror("sixel_parser_init() failed"); + term.mode |= MODE_SIXEL; + break; + } +} + void csihandle(void) { char buf[40]; int len; + ImageList *im; switch (csiescseq.mode[0]) { default: @@ -1697,6 +1716,13 @@ csihandle(void) tputtab(csiescseq.arg[0]); break; case 'J': /* ED -- Clear screen */ + /* purge sixels */ + /* TODO: kinda gross, should probably make this only purge + * visible sixels + */ + for (im = term.images; im; im = im->next) + im->should_delete = 1; + switch (csiescseq.arg[0]) { case 0: /* below */ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); @@ -1844,6 +1870,8 @@ strhandle(void) { char *p = NULL, *dec; int j, narg, par; + ImageList *new_image; + int i; term.esc &= ~(ESC_STR_END|ESC_STR); strparse(); @@ -1903,6 +1931,39 @@ strhandle(void) xsettitle(strescseq.args[0]); return; case 'P': /* DCS -- Device Control String */ + if (IS_SET(MODE_SIXEL)) { + term.mode &= ~MODE_SIXEL; + new_image = malloc(sizeof(ImageList)); + memset(new_image, 0, sizeof(ImageList)); + new_image->x = term.c.x; + new_image->y = term.c.y; + new_image->width = sixel_st.image.width; + new_image->height = sixel_st.image.height; + new_image->pixels = malloc(new_image->width * new_image->height * 4); + if (sixel_parser_finalize(&sixel_st, new_image->pixels) != 0) { + perror("sixel_parser_finalize() failed"); + sixel_parser_deinit(&sixel_st); + return; + } + sixel_parser_deinit(&sixel_st); + if (term.images) { + ImageList *im; + for (im = term.images; im->next;) + im = im->next; + im->next = new_image; + new_image->prev = im; + } else { + term.images = new_image; + } + for (i = 0; i < (sixel_st.image.height + win.ch-1)/win.ch; ++i) { + int x; + tclearregion(term.c.x, term.c.y, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw, term.c.y); + for (x = term.c.x; x < MIN(term.col, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw); x++) + term.line[term.c.y][x].mode |= ATTR_SIXEL; + tnewline(1); + } + } + return; case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ return; @@ -2230,6 +2291,7 @@ eschandle(uchar ascii) term.esc |= ESC_UTF8; return 0; case 'P': /* DCS -- Device Control String */ + term.esc |= ESC_STR; case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ case ']': /* OSC -- Operating System Command */ @@ -2334,6 +2396,15 @@ tputc(Rune u) goto check_control_code; } + if (IS_SET(MODE_SIXEL)) { + if (sixel_parser_parse(&sixel_st, (unsigned char *)&u, 1) != 0) + perror("sixel_parser_parse() failed"); + return; + } + + if (term.esc & ESC_STR) + goto check_control_code; + if (strescseq.len+len >= strescseq.siz) { /* * Here is a bug in terminals. If the user never sends @@ -2384,7 +2455,16 @@ check_control_code: csihandle(); } return; - } else if (term.esc & ESC_UTF8) { + } else if (term.esc & ESC_STR) { + 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) { tdeftran(u); diff --git a/st.c.orig b/st.c.orig new file mode 100644 index 0000000..abbbe4b --- /dev/null +++ b/st.c.orig @@ -0,0 +1,2605 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* 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)) + +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, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + 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 '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(int *, int); +static void tsetchar(Rune, Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, 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(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 char *base64dec(const char *); +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; + +static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(char *s) +{ + if ((s = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return s; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + 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) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + 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)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* 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; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *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)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + 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; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +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; + + 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 +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(char *line, char *cmd, char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + close(s); + close(m); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, Glyph *attr, int x, int y) +{ + static char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, int *args, int narg) +{ + int alt, *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + 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); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-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); + break; + default: + goto unknown; + } + break; + 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); + 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); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset, here p = NULL */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + 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_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, 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]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + 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++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + 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; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st.c.rej b/st.c.rej new file mode 100644 index 0000000..950f433 --- /dev/null +++ b/st.c.rej @@ -0,0 +1,119 @@ + +/* 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; + + +--- st.c ++++ st.c +@@ -105,26 +99,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; +-} Term; +- + /* CSI Escape sequence structs */ + /* ESC '[' [[ [] [;]] []] */ + typedef struct { +@@ -1929,7 +1956,39 @@ strhandle(void) + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ +- term.mode |= ESC_DCS; ++ if (IS_SET(MODE_SIXEL)) { ++ term.mode &= ~MODE_SIXEL; ++ new_image = malloc(sizeof(ImageList)); ++ memset(new_image, 0, sizeof(ImageList)); ++ new_image->x = term.c.x; ++ new_image->y = term.c.y; ++ new_image->width = sixel_st.image.width; ++ new_image->height = sixel_st.image.height; ++ new_image->pixels = malloc(new_image->width * new_image->height * 4); ++ if (sixel_parser_finalize(&sixel_st, new_image->pixels) != 0) { ++ perror("sixel_parser_finalize() failed"); ++ sixel_parser_deinit(&sixel_st); ++ return; ++ } ++ sixel_parser_deinit(&sixel_st); ++ if (term.images) { ++ ImageList *im; ++ for (im = term.images; im->next;) ++ im = im->next; ++ im->next = new_image; ++ new_image->prev = im; ++ } else { ++ term.images = new_image; ++ } ++ for (i = 0; i < (sixel_st.image.height + win.ch-1)/win.ch; ++i) { ++ int x; ++ tclearregion(term.c.x, term.c.y, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw, term.c.y); ++ for (x = term.c.x; x < MIN(term.col, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw); x++) ++ term.line[term.c.y][x].mode |= ATTR_SIXEL; ++ tnewline(1); ++ } ++ } ++ return; + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; +@@ -2358,21 +2418,17 @@ tputc(Rune u) + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); +- if (IS_SET(MODE_SIXEL)) { +- /* TODO: render sixel */; +- term.mode &= ~MODE_SIXEL; +- return; +- } + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (IS_SET(MODE_SIXEL)) { +- /* TODO: implement sixel mode */ ++ if (sixel_parser_parse(&sixel_st, (unsigned char *)&u, 1) != 0) ++ perror("sixel_parser_parse() failed"); + return; + } +- if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') +- term.mode |= MODE_SIXEL; ++ if (term.esc & ESC_DCS) ++ goto check_control_code; + + if (strescseq.len+len >= sizeof(strescseq.buf)-1) { + /* diff --git a/st.h b/st.h index 3d351b6..e8e49de 100644 --- a/st.h +++ b/st.h @@ -33,6 +33,7 @@ enum glyph_attribute { ATTR_WRAP = 1 << 8, ATTR_WIDE = 1 << 9, ATTR_WDUMMY = 1 << 10, + ATTR_SIXEL = 1 << 11, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; @@ -77,6 +78,57 @@ typedef union { const char *s; } Arg; +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct _ImageList { + struct _ImageList *next, *prev; + unsigned char *pixels; + void *pixmap; + int width; + int height; + int x; + int y; + int should_delete; +} ImageList; + +/* 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; +} Term; + +/* 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; + void die(const char *, ...); void redraw(void); void draw(void); @@ -123,3 +175,5 @@ extern char *termname; extern unsigned int tabspaces; extern unsigned int defaultfg; extern unsigned int defaultbg; +extern Term term; +extern TermWindow win; diff --git a/st.h.orig b/st.h.orig new file mode 100644 index 0000000..3d351b6 --- /dev/null +++ b/st.h.orig @@ -0,0 +1,125 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#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_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(char *, char *, char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(char *); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; diff --git a/st.o b/st.o index 84b985683646f9aa14310a244c3c190073263618..a88201f65ad7fcfdfbd507d145aa3aa64bcce9ec 100644 GIT binary patch literal 75112 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 literal 73752 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%0AaZlRtD#oh6tu%e!Q=< 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 Yie_Tl^B*CfEPZ!EJP&)dJnXFgKan6pe*gdg diff --git a/x.c b/x.c index 3c7fc47..bb87e18 100644 --- a/x.c +++ b/x.c @@ -77,16 +77,6 @@ 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; @@ -219,7 +209,7 @@ static void (*handler[LASTEvent])(XEvent *) = { static DC dc; static XWindow xw; static XSelection xsel; -static TermWindow win; +TermWindow win; /* Font Ring Cache */ enum { @@ -1645,14 +1635,112 @@ xdrawline(Line line, int x1, int y1, int x2) xdrawglyphfontspecs(specs, base, i, ox, y1); } +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); + free(im->pixels); + free(im); +} + void xfinishdraw(void) { + ImageList *im; + int x, y; + int n = 0; + int nlimit = 256; + XRectangle *rects = NULL; + XGCValues gcvalues; + GC gc; + + for (im = term.images; im; im = im->next) { + if (im->should_delete) { + delete_image(im); + + /* + * prevent the next iteration from + * accessing an invalid pointer + */ + im = term.images; + if (im == NULL) + break; + else + continue; + } + if (!im->pixmap) { + im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, im->width, im->height, DefaultDepth(xw.dpy, xw.scr)); + XImage ximage = { + .format = ZPixmap, + .data = (char *)im->pixels, + .width = im->width, + .height = im->height, + .xoffset = 0, + .byte_order = LSBFirst, + .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, im->width, im->height); + free(im->pixels); + im->pixels = NULL; + } + n = 0; + memset(&gcvalues, 0, sizeof(gcvalues)); + gc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues); + for (y = im->y; y < im->y + (im->height+win.ch-1)/win.ch; y++) { + if (y >= 0 && y < term.row) { + for (x = im->x; x < im->x + (im->width+win.cw-1)/win.cw; x++) { + if (!rects) + rects = xmalloc(sizeof(XRectangle) * nlimit); + if (term.line[y][x].mode & ATTR_SIXEL) { + if (n > 0 && rects[n-1].x+rects[n-1].width == borderpx+x*win.cw && rects[n-1].y == borderpx+y*win.ch) { + rects[n-1].width += win.cw; + } else { + rects[n].x = borderpx+x*win.cw; + rects[n].y = borderpx+y*win.ch; + rects[n].width = win.cw; + rects[n].height = win.ch; + if (++n == nlimit && (rects = realloc(rects, sizeof(XRectangle) * (nlimit *= 2))) == NULL) + die("Out of memory\n"); + } + } + } + } + if (n > 1 && rects[n-2].x == rects[n-1].x && rects[n-2].width == rects[n-1].width) { + if (rects[n-2].y+rects[n-2].height == rects[n-1].y) { + rects[n-2].height += win.ch; + n--; + } + } + } + if (n == 0) { + delete_image(im); + continue; + } + if (n > 1) + XSetClipRectangles(xw.dpy, gc, 0, 0, rects, n, YXSorted); + XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, im->width, im->height, borderpx + im->x * win.cw, borderpx + im->y * win.ch); + 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); + + free(rects); } void diff --git a/x.c.orig b/x.c.orig new file mode 100644 index 0000000..3c7fc47 --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2065 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#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; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(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" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#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); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +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 *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static int oldbutton = 3; /* button event on startup: 3 = release */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + 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 Buttonmask for Button - 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) +{ + struct timespec now; + int snap; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + 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 + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + 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->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + 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) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +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; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + 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; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +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, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + 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, 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. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + 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 */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (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, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + if (IS_SET(MODE_VISIBLE)) + XCopyArea(xw.dpy, xw.win, xw.buf, dc.gc, 0, 0, win.w, win.h, 0, 0); + return IS_SET(MODE_VISIBLE); +} + +void +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); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + 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); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * 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 (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +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); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/x.o b/x.o index 31b3f4a0668f457b81c3f7eba50ca9f6bb3a7a5e..c998a664ad6f61091fda46b32f62839a9c26dfe9 100644 GIT binary patch literal 77608 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 literal 74680 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