Terminal compatibility batch

- Answer kitty keyboard protocol queries (CSI ?u -> CSI ?0u) and silently
  accept push/pop/set so clients detect the lack of support without a
  timeout, and so st stops logging them as unknown sequences.
- XTVERSION (CSI >0q) reports st(VERSION).
- DECRQM (CSI ?Ps$p / CSI Ps$p) reports the state of the common private
  and ANSI modes including 2026 (synchronized updates) and 2004.
- csiparse() now recognises the >, < and = private prefixes.
- New OSC7_OSC8_PATCH: OSC 7 stores the shell's working directory in
  term.cwd; OSC 8 hyperlinks are stored per cell and, with the
  openurlonclick patch, are clickable and take precedence over URL text
  detection.
- kitty: Unicode placeholder cells are now blitted from one cached scaled
  pixmap per virtual placement instead of rescaling every cell on every
  redraw.
This commit is contained in:
a 2026-09-06 22:36:33 -05:00
parent 56c4253a87
commit 3a4e096d9b
No known key found for this signature in database
GPG Key ID: 2F22877AA4DFDADB
7 changed files with 317 additions and 99 deletions

112
kitty.c
View File

@ -2295,123 +2295,47 @@ kitty_find_virtual(uint32_t imgid, uint32_t plcid)
/* /*
* Blit the part of a virtual placement that corresponds to one screen cell. * Blit the part of a virtual placement that corresponds to one screen cell.
* (px, py) is the cell on screen, (row, col) the cell within the placement. * (px, py) is the cell on screen, (row, col) the cell within the placement.
*
* The whole placement is scaled once into the placement's pixmap cache
* (kitty_prepare), and each cell is then a cheap XRender copy out of it.
*/ */
static void static void
kitty_blit_cell(KittyPlacement *p, int px, int py, int row, int col) kitty_blit_cell(KittyPlacement *p, int px, int py, int row, int col)
{ {
KittyImage *im = p->image;
const unsigned char *base;
unsigned char *cell;
int cw = win.cw, ch = win.ch;
int fullw, fullh, sx0, sy0, x, y, i, n;
unsigned char *tile;
Pixmap pm;
XImage ximage;
Picture src, dstpic; Picture src, dstpic;
XRenderPictFormat *fmt; XRenderPictFormat *fmt;
XRenderPictureAttributes pa; XRenderPictureAttributes pa;
int bx, by; int w, h, bx, by, cw = win.cw, ch = win.ch;
int sx, sy, cellw, cellh;
if (!im || row >= p->rows || col >= p->cols) if (!p->image || row >= p->rows || col >= p->cols)
return;
if (!kitty_prepare(p, &w, &h))
return; return;
fullw = p->cols * cw; sx = col * cw;
fullh = p->rows * ch; sy = row * ch;
if (fullw <= 0 || fullh <= 0) cellw = KMIN(cw, w - sx);
cellh = KMIN(ch, h - sy);
if (cellw <= 0 || cellh <= 0)
return; return;
base = kitty_frame_pixels(im, im->current_frame);
if (!base)
return;
base += ((size_t)p->sy * im->width + p->sx) * 4;
/* scale just this cell out of the source rect */
sx0 = col * cw;
sy0 = row * ch;
if (!(cell = malloc((size_t)cw * ch * 4)))
return;
for (y = 0; y < ch; y++) {
double fy = ((double)(sy0 + y) + 0.5) * p->sh / fullh - 0.5;
int y0 = (int)(fy < 0 ? 0 : fy);
int y1 = KMIN(y0 + 1, p->sh - 1);
int wy = (int)((fy - y0) * 256);
if (wy < 0) wy = 0;
if (wy > 256) wy = 256;
for (x = 0; x < cw; x++) {
double fx = ((double)(sx0 + x) + 0.5) * p->sw / fullw - 0.5;
int x0 = (int)(fx < 0 ? 0 : fx);
int x1 = KMIN(x0 + 1, p->sw - 1);
int wx = (int)((fx - x0) * 256);
const unsigned char *q00, *q01, *q10, *q11;
unsigned char *o = cell + ((size_t)y * cw + x) * 4;
int c;
if (wx < 0) wx = 0;
if (wx > 256) wx = 256;
q00 = base + (size_t)y0 * im->width * 4 + x0 * 4;
q01 = base + (size_t)y0 * im->width * 4 + x1 * 4;
q10 = base + (size_t)y1 * im->width * 4 + x0 * 4;
q11 = base + (size_t)y1 * im->width * 4 + x1 * 4;
for (c = 0; c < 4; c++) {
int t = q00[c] * (256 - wx) + q01[c] * wx;
int b = q10[c] * (256 - wx) + q11[c] * wx;
o[c] = (unsigned char)((t * (256 - wy) + b * wy) >> 16);
}
}
}
tile = cell;
n = cw * ch;
for (i = 0; i < n; i++) {
unsigned char r = tile[4*i+0], g = tile[4*i+1];
unsigned char b = tile[4*i+2], a = tile[4*i+3];
tile[4*i+0] = (unsigned char)(b * a / 255);
tile[4*i+1] = (unsigned char)(g * a / 255);
tile[4*i+2] = (unsigned char)(r * a / 255);
tile[4*i+3] = a;
}
pm = XCreatePixmap(xw.dpy, xw.win, cw, ch, 32);
if (!pm) {
free(cell);
return;
}
memset(&ximage, 0, sizeof ximage);
ximage.format = ZPixmap;
ximage.data = (char *)tile;
ximage.width = cw;
ximage.height = ch;
ximage.byte_order = LSBFirst;
ximage.bitmap_bit_order = MSBFirst;
ximage.bits_per_pixel = 32;
ximage.bytes_per_line = cw * 4;
ximage.bitmap_unit = 32;
ximage.bitmap_pad = 32;
ximage.depth = 32;
XInitImage(&ximage);
{
GC gc = XCreateGC(xw.dpy, pm, 0, NULL);
XPutImage(xw.dpy, pm, gc, &ximage, 0, 0, 0, 0, cw, ch);
XFreeGC(xw.dpy, gc);
}
free(cell);
kitty_border(&bx, &by); kitty_border(&bx, &by);
memset(&pa, 0, sizeof pa); memset(&pa, 0, sizeof pa);
fmt = XRenderFindStandardFormat(xw.dpy, PictStandardARGB32); fmt = XRenderFindStandardFormat(xw.dpy, PictStandardARGB32);
if (fmt) { if (!fmt)
src = XRenderCreatePicture(xw.dpy, pm, fmt, 0, &pa); return;
src = XRenderCreatePicture(xw.dpy, (Drawable)p->pixmap, fmt, 0, &pa);
fmt = XRenderFindVisualFormat(xw.dpy, xw.vis); fmt = XRenderFindVisualFormat(xw.dpy, xw.vis);
if (fmt) { if (fmt) {
dstpic = XRenderCreatePicture(xw.dpy, xw.buf, fmt, 0, &pa); dstpic = XRenderCreatePicture(xw.dpy, xw.buf, fmt, 0, &pa);
XRenderComposite(xw.dpy, PictOpOver, src, None, dstpic, 0, 0, 0, 0, XRenderComposite(xw.dpy, PictOpOver, src, None, dstpic, sx, sy, 0, 0,
bx + px * cw, by + py * ch, cw, ch); bx + px * cw, by + py * ch, cellw, cellh);
XRenderFreePicture(xw.dpy, dstpic); XRenderFreePicture(xw.dpy, dstpic);
} }
XRenderFreePicture(xw.dpy, src); XRenderFreePicture(xw.dpy, src);
} }
XFreePixmap(xw.dpy, pm);
}
/* scan the visible screen for placeholder glyphs and draw their images */ /* scan the visible screen for placeholder glyphs and draw their images */
static void static void

View File

@ -61,6 +61,30 @@ clearurl(void)
url_y2 = -1; url_y2 = -1;
} }
#if OSC7_OSC8_PATCH
/* an explicit OSC 8 hyperlink takes precedence over text detection */
static char *
detecthyperlink(int col, int row, int draw)
{
int x1, y1, x2, y2;
uint32_t id = hyperlink_at(col, row, &x1, &y1, &x2, &y2);
const char *url;
if (!id || !(url = hyperlink_get(id)))
return NULL;
if (draw) {
url_x1 = x1;
url_x2 = x2;
url_y1 = y1;
url_y2 = y2;
url_maxcol = x2;
url_draw = 1;
term.dirty[y1] = 1;
}
return (char *)url;
}
#endif // OSC7_OSC8_PATCH
#if REFLOW_PATCH #if REFLOW_PATCH
char * char *
detecturl(int col, int row, int draw) detecturl(int col, int row, int draw)
@ -72,11 +96,19 @@ detecturl(int col, int row, int draw)
int row_start = row, col_start = col; int row_start = row, col_start = col;
int minrow = tisaltscr() ? 0 : term.scr - term.histf; int minrow = tisaltscr() ? 0 : term.scr - term.histf;
int maxrow = tisaltscr() ? term.row - 1 : term.scr + term.row - 1; int maxrow = tisaltscr() ? term.row - 1 : term.scr + term.row - 1;
#if OSC7_OSC8_PATCH
char *link;
#endif // OSC7_OSC8_PATCH
/* clear previously underlined url */ /* clear previously underlined url */
if (draw) if (draw)
clearurl(); clearurl();
#if OSC7_OSC8_PATCH
if ((link = detecthyperlink(col, row, draw)))
return link;
#endif // OSC7_OSC8_PATCH
url_maxcol = 0; url_maxcol = 0;
line = TLINE(row); line = TLINE(row);
@ -143,6 +175,9 @@ char *
detecturl(int col, int row, int draw) detecturl(int col, int row, int draw)
{ {
static char url[2048]; static char url[2048];
#if OSC7_OSC8_PATCH
char *link;
#endif // OSC7_OSC8_PATCH
int x1, y1, x2, y2, wrapped; int x1, y1, x2, y2, wrapped;
int row_start = row; int row_start = row;
int col_start = col; int col_start = col;
@ -162,6 +197,11 @@ detecturl(int col, int row, int draw)
if (draw) if (draw)
clearurl(); clearurl();
#if OSC7_OSC8_PATCH
if ((link = detecthyperlink(col, row, draw)))
return link;
#endif // OSC7_OSC8_PATCH
if (!isvalidurlchar(TLINEURL(row)[col].u)) if (!isvalidurlchar(TLINEURL(row)[col].u))
return NULL; return NULL;

View File

@ -68,6 +68,9 @@ tclearglyph(Glyph *gp, int usecurattr)
#if KITTY_GRAPHICS_PATCH #if KITTY_GRAPHICS_PATCH
gp->kitty = 0; gp->kitty = 0;
#endif // KITTY_GRAPHICS_PATCH #endif // KITTY_GRAPHICS_PATCH
#if OSC7_OSC8_PATCH
gp->link = 0;
#endif // OSC7_OSC8_PATCH
} }
#if SIXEL_PATCH #if SIXEL_PATCH

View File

@ -357,6 +357,14 @@
*/ */
#define KITTY_GRAPHICS_PATCH 0 #define KITTY_GRAPHICS_PATCH 0
/* This patch adds support for OSC 7 (current working directory reporting) and
* OSC 8 (hyperlinks). Shells and programs can mark ranges of text as links to
* arbitrary URLs; with the openurlonclick patch these become clickable and
* take precedence over URL text detection. The working directory reported by
* the shell is stored in term.cwd for use by e.g. new window shortcuts.
*/
#define OSC7_OSC8_PATCH 0
/* This patch adds SIXEL graphics support for st. /* This patch adds SIXEL graphics support for st.
* Note that patch/sixel.c/sixel_hls.c come from mintty, licensed under GPL. * Note that patch/sixel.c/sixel_hls.c come from mintty, licensed under GPL.
* Known issues: * Known issues:

View File

@ -357,6 +357,14 @@
*/ */
#define KITTY_GRAPHICS_PATCH 1 #define KITTY_GRAPHICS_PATCH 1
/* This patch adds support for OSC 7 (current working directory reporting) and
* OSC 8 (hyperlinks). Shells and programs can mark ranges of text as links to
* arbitrary URLs; with the openurlonclick patch these become clickable and
* take precedence over URL text detection. The working directory reported by
* the shell is stored in term.cwd for use by e.g. new window shortcuts.
*/
#define OSC7_OSC8_PATCH 1
/* This patch adds SIXEL graphics support for st. /* This patch adds SIXEL graphics support for st.
* Note that patch/sixel.c/sixel_hls.c come from mintty, licensed under GPL. * Note that patch/sixel.c/sixel_hls.c come from mintty, licensed under GPL.
* Known issues: * Known issues:

226
st.c
View File

@ -143,6 +143,7 @@ typedef struct {
char buf[ESC_BUF_SIZ]; /* raw string */ char buf[ESC_BUF_SIZ]; /* raw string */
size_t len; /* raw string length */ size_t len; /* raw string length */
char priv; char priv;
char prefix; /* '>', '<' or '=' private prefix */
int arg[ESC_ARG_SIZ]; int arg[ESC_ARG_SIZ];
int narg; /* nb of args */ int narg; /* nb of args */
char mode[2]; char mode[2];
@ -226,6 +227,7 @@ static void tsetdirt(int, int);
static void tsetscroll(int, int); static void tsetscroll(int, int);
static void tswapscreen(void); static void tswapscreen(void);
static void tsetmode(int, int, const int *, int); static void tsetmode(int, int, const int *, int);
static int tmodestatus(int, int);
static int twrite(const char *, int, int); static int twrite(const char *, int, int);
static void tcontrolcode(uchar ); static void tcontrolcode(uchar );
static void tdectest(char ); static void tdectest(char );
@ -270,6 +272,115 @@ static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF
#include "patch/st_include.h" #include "patch/st_include.h"
#if OSC7_OSC8_PATCH
/*
* OSC 8 hyperlinks. Each distinct (id, url) pair gets a numeric handle that
* is stored in Glyph.link. Links with an explicit id= parameter are shared
* between all cells using that id, so a link split across lines still counts
* as one link.
*/
typedef struct {
char *id; /* the id= parameter, or NULL */
char *url;
} Hyperlink;
static Hyperlink *hyperlinks; /* index 0 is unused (0 == no link) */
static uint32_t nhyperlinks, hyperlinkcap;
static uint32_t curlink; /* link applied to newly printed cells */
static uint32_t
hyperlink_add(const char *id, const char *url)
{
uint32_t i;
Hyperlink *h;
if (!url || !*url)
return 0;
/* reuse an existing entry for the same id and url */
for (i = 1; i < nhyperlinks; i++) {
h = &hyperlinks[i];
if (!strcmp(h->url, url) &&
((!id && !h->id) || (id && h->id && !strcmp(id, h->id))))
return i;
}
if (nhyperlinks + 1 > hyperlinkcap) {
uint32_t ncap = hyperlinkcap ? hyperlinkcap * 2 : 64;
hyperlinks = xrealloc(hyperlinks, ncap * sizeof *hyperlinks);
if (!nhyperlinks) {
hyperlinks[0].id = hyperlinks[0].url = NULL;
nhyperlinks = 1;
}
hyperlinkcap = ncap;
}
h = &hyperlinks[nhyperlinks];
h->id = (id && *id) ? xstrdup(id) : NULL;
h->url = xstrdup(url);
return nhyperlinks++;
}
const char *
hyperlink_get(uint32_t id)
{
if (id == 0 || id >= nhyperlinks)
return NULL;
return hyperlinks[id].url;
}
/* handle "OSC 8 ; params ; url": params is a ':'-separated key=value list */
static void
hyperlink_osc(const char *params, const char *url)
{
char idbuf[256], *id = NULL;
const char *p = params;
while (p && *p) {
const char *end = strchr(p, ':');
size_t n = end ? (size_t)(end - p) : strlen(p);
if (n > 3 && !strncmp(p, "id=", 3)) {
n = MIN(n - 3, sizeof idbuf - 1);
memcpy(idbuf, p + 3, n);
idbuf[n] = '\0';
id = idbuf;
}
p = end ? end + 1 : NULL;
}
curlink = hyperlink_add(id, url);
}
/*
* Find the extent of the link at (col, row) on the visible screen. Returns
* the link handle, or 0 if the cell is not part of a link. The returned
* rectangle covers the contiguous run on this row; use hyperlink_get() for
* the url.
*/
uint32_t
hyperlink_at(int col, int row, int *x1, int *y1, int *x2, int *y2)
{
Line line;
uint32_t id;
int x;
if (row < 0 || row >= term.row || col < 0 || col >= term.col)
return 0;
#if REFLOW_PATCH || SCROLLBACK_PATCH
line = TLINE(row);
#else
line = term.line[row];
#endif
if (!(id = line[col].link))
return 0;
for (x = col; x > 0 && line[x-1].link == id; x--);
*x1 = x;
for (x = col; x < term.col-1 && line[x+1].link == id; x++);
*x2 = x;
*y1 = *y2 = row;
return id;
}
#endif // OSC7_OSC8_PATCH
ssize_t ssize_t
xwrite(int fd, const char *s, size_t len) xwrite(int fd, const char *s, size_t len)
{ {
@ -1204,6 +1315,9 @@ treset(void)
term.top = 0; term.top = 0;
term.bot = term.row - 1; term.bot = term.row - 1;
term.mode = MODE_WRAP|MODE_UTF8; term.mode = MODE_WRAP|MODE_UTF8;
#if OSC7_OSC8_PATCH
curlink = 0;
#endif // OSC7_OSC8_PATCH
memset(term.trantbl, CS_USA, sizeof(term.trantbl)); memset(term.trantbl, CS_USA, sizeof(term.trantbl));
term.charset = 0; term.charset = 0;
#if REFLOW_PATCH #if REFLOW_PATCH
@ -1508,9 +1622,13 @@ csiparse(void)
int sep = ';'; /* colon or semi-colon, but not both */ int sep = ';'; /* colon or semi-colon, but not both */
csiescseq.narg = 0; csiescseq.narg = 0;
csiescseq.prefix = '\0';
if (*p == '?') { if (*p == '?') {
csiescseq.priv = 1; csiescseq.priv = 1;
p++; p++;
} else if (*p == '>' || *p == '<' || *p == '=') {
csiescseq.prefix = *p;
p++;
} }
csiescseq.buf[csiescseq.len] = '\0'; csiescseq.buf[csiescseq.len] = '\0';
@ -1610,6 +1728,10 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
term.line[y][x].mode |= ATTR_BOXDRAW; term.line[y][x].mode |= ATTR_BOXDRAW;
#endif // BOXDRAW_PATCH #endif // BOXDRAW_PATCH
#if OSC7_OSC8_PATCH
term.line[y][x].link = curlink;
#endif // OSC7_OSC8_PATCH
#if KITTY_GRAPHICS_PATCH #if KITTY_GRAPHICS_PATCH
term.line[y][x].kitty = 0; term.line[y][x].kitty = 0;
if (u == KITTY_PLACEHOLDER) if (u == KITTY_PLACEHOLDER)
@ -1951,6 +2073,49 @@ tsetscroll(int t, int b)
term.bot = b; term.bot = b;
} }
/* DECRQM: report the state of a mode */
static int
tmodestatus(int priv, int mode)
{
int wm = win.mode;
if (priv) {
switch (mode) {
case 1: return (wm & MODE_APPCURSOR) ? 1 : 2;
case 6: return (term.c.state & CURSOR_ORIGIN) ? 1 : 2;
case 7: return IS_SET(MODE_WRAP) ? 1 : 2;
case 12: return 0;
case 25: return (wm & MODE_HIDE) ? 2 : 1;
case 47:
case 1047:
case 1049: return IS_SET(MODE_ALTSCREEN) ? 1 : 2;
case 9: return (wm & MODE_MOUSEX10) ? 1 : 2;
case 1000: return (wm & MODE_MOUSEBTN) ? 1 : 2;
case 1002: return (wm & MODE_MOUSEMOTION) ? 1 : 2;
case 1003: return (wm & MODE_MOUSEMANY) ? 1 : 2;
case 1004: return (wm & MODE_FOCUS) ? 1 : 2;
case 1006: return (wm & MODE_MOUSESGR) ? 1 : 2;
case 1034: return (wm & MODE_8BIT) ? 1 : 2;
case 2004: return (wm & MODE_BRCKTPASTE) ? 1 : 2;
#if SYNC_PATCH
case 2026: return su ? 1 : 2;
#endif // SYNC_PATCH
#if SIXEL_PATCH
case 80: return IS_SET(MODE_SIXEL_SDM) ? 1 : 2;
case 8452: return IS_SET(MODE_SIXEL_CUR_RT) ? 1 : 2;
#endif // SIXEL_PATCH
}
return 0;
}
switch (mode) {
case 2: return 4; /* KAM, always reset */
case 4: return IS_SET(MODE_INSERT) ? 1 : 2;
case 12: return IS_SET(MODE_ECHO) ? 2 : 1;
case 20: return IS_SET(MODE_CRLF) ? 1 : 2;
}
return 0;
}
void void
tsetmode(int priv, int set, const int *args, int narg) tsetmode(int priv, int set, const int *args, int narg)
{ {
@ -2535,9 +2700,47 @@ csihandle(void)
} }
break; break;
#endif // CSI_22_23_PATCH | SIXEL_PATCH #endif // CSI_22_23_PATCH | SIXEL_PATCH
case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ case 'u':
if (csiescseq.priv) {
/* CSI ? u -- kitty keyboard protocol: query flags. We do not
* implement the protocol, so answer with no flags set. */
ttywrite("\033[?0u", 5, 0);
break;
}
if (csiescseq.prefix == '>' || csiescseq.prefix == '<' ||
csiescseq.prefix == '=') {
/* CSI > flags u (push), CSI < flags u (pop), CSI = flags ; mode u
* (set): kitty keyboard protocol, silently ignored. */
break;
}
/* DECRC -- Restore cursor position (ANSI.SYS) */
tcursor(CURSOR_LOAD); tcursor(CURSOR_LOAD);
break; break;
case 'q':
if (csiescseq.prefix == '>') {
/* XTVERSION -- CSI > Ps q: report terminal name and version */
n = snprintf(buffer, sizeof(buffer), "\033P>|st(%s)\033\\", VERSION);
ttywrite(buffer, n, 0);
break;
}
goto unknown;
case '$':
switch (csiescseq.mode[1]) {
case 'p': /* DECRQM -- request mode */
/*
* Reply: CSI ? Pd ; Ps $ y (ANSI: CSI Pd ; Ps $ y)
* Ps: 0 not recognized, 1 set, 2 reset,
* 3 permanently set, 4 permanently reset
*/
n = snprintf(buffer, sizeof(buffer), "\033[%s%d;%d$y",
csiescseq.priv ? "?" : "", csiescseq.arg[0],
tmodestatus(csiescseq.priv, csiescseq.arg[0]));
ttywrite(buffer, n, 0);
break;
default:
goto unknown;
}
break;
case ' ': case ' ':
switch (csiescseq.mode[1]) { switch (csiescseq.mode[1]) {
case 'q': /* DECSCUSR -- Set Cursor Style */ case 'q': /* DECSCUSR -- Set Cursor Style */
@ -2678,8 +2881,29 @@ strhandle(void)
} }
} }
return; return;
#if OSC7_OSC8_PATCH
case 7: /* current working directory: file://host/path */
if (narg > 1) {
p = strescseq.args[1];
if (!strncmp(p, "file://", 7)) {
/* skip the host part */
p += 7;
p = strchr(p, '/');
}
free(term.cwd);
term.cwd = (p && *p == '/') ? xstrdup(p) : NULL;
}
return;
case 8: /* hyperlink: OSC 8 ; params ; url */
if (narg > 2 && strescseq.args[2][0])
hyperlink_osc(strescseq.args[1], strescseq.args[2]);
else
curlink = 0;
return;
#else
case 8: /* Clear Hyperlinks */ case 8: /* Clear Hyperlinks */
return; return;
#endif // OSC7_OSC8_PATCH
case 10: case 10:
case 11: case 11:
case 12: case 12:

11
st.h
View File

@ -150,6 +150,9 @@ typedef struct {
#if KITTY_GRAPHICS_PATCH #if KITTY_GRAPHICS_PATCH
uint32_t kitty; /* kitty Unicode placeholder row/column/msb */ uint32_t kitty; /* kitty Unicode placeholder row/column/msb */
#endif // KITTY_GRAPHICS_PATCH #endif // KITTY_GRAPHICS_PATCH
#if OSC7_OSC8_PATCH
uint32_t link; /* OSC 8 hyperlink id, 0 == none */
#endif // OSC7_OSC8_PATCH
} Glyph; } Glyph;
typedef Glyph *Line; typedef Glyph *Line;
@ -208,6 +211,9 @@ typedef struct {
ImageList *images_alt; /* sixel images for alternate screen */ ImageList *images_alt; /* sixel images for alternate screen */
#endif // SIXEL_PATCH #endif // SIXEL_PATCH
Rune lastc; /* last printed char outside of sequence, 0 if control */ Rune lastc; /* last printed char outside of sequence, 0 if control */
#if OSC7_OSC8_PATCH
char *cwd; /* working directory reported via OSC 7, or NULL */
#endif // OSC7_OSC8_PATCH
} Term; } Term;
typedef union { typedef union {
@ -376,6 +382,11 @@ char *getsel(void);
size_t utf8encode(Rune, char *); size_t utf8encode(Rune, char *);
#if OSC7_OSC8_PATCH
const char *hyperlink_get(uint32_t id);
uint32_t hyperlink_at(int col, int row, int *x1, int *y1, int *x2, int *y2);
#endif // OSC7_OSC8_PATCH
void *xmalloc(size_t); void *xmalloc(size_t);
void *xrealloc(void *, size_t); void *xrealloc(void *, size_t);
char *xstrdup(const char *); char *xstrdup(const char *);