tmux compatibility

- New allowclipboard config flag (default on) so OSC 52 works without
  enabling all window ops. tmux set-clipboard was silently dropped before.
- DA2 (CSI >c) and DA3 (CSI =c) answered; tmux fingerprints with these.
- DSR 996 reports the colour scheme (dark/light) from the background.
- DECRQM 2031 reported as permanently reset.
- Unknown DCS sequences (DCS tmux; passthrough, XTGETTCAP) are discarded
  until ST instead of having their body interpreted as text/CSI.
- Private mode 7727 (extended keys) silently ignored.
- st.info: add RGB, Smulx, Setulc, kxIN, kxOUT and Hls so tmux and
  ncurses apps use truecolor, styled underlines, focus events and links.
This commit is contained in:
a 2026-09-07 00:46:10 -05:00
parent 6fdc12966f
commit 90f40ce703
No known key found for this signature in database
GPG Key ID: 2F22877AA4DFDADB
8 changed files with 75 additions and 4 deletions

BIN
.st.h.swn Normal file

Binary file not shown.

BIN
.st.h.swo Normal file

Binary file not shown.

BIN
.st.h.swp Normal file

Binary file not shown.

View File

@ -90,6 +90,10 @@ int allowaltscreen = 1;
setting the clipboard text */ setting the clipboard text */
int allowwindowops = 0; int allowwindowops = 0;
/* allow programs to set the clipboard via OSC 52. This is what tmux uses
for set-clipboard, and what remote programs use to copy over ssh. */
int allowclipboard = 1;
/* /*
* draw latency range in ms - from new content/keypress/etc until drawing. * draw latency range in ms - from new content/keypress/etc until drawing.
* within this range, st draws when content stops arriving (idle). mostly it's * within this range, st draws when content stops arriving (idle). mostly it's

View File

@ -90,6 +90,10 @@ int allowaltscreen = 1;
setting the clipboard text */ setting the clipboard text */
int allowwindowops = 0; int allowwindowops = 0;
/* allow programs to set the clipboard via OSC 52. This is what tmux uses
for set-clipboard, and what remote programs use to copy over ssh. */
int allowclipboard = 1;
/* /*
* draw latency range in ms - from new content/keypress/etc until drawing. * draw latency range in ms - from new content/keypress/etc until drawing.
* within this range, st draws when content stops arriving (idle). mostly it's * within this range, st draws when content stops arriving (idle). mostly it's

61
st.c
View File

@ -117,6 +117,7 @@ enum escape_state {
#if SIXEL_PATCH #if SIXEL_PATCH
ESC_DCS =128, ESC_DCS =128,
#endif // SIXEL_PATCH #endif // SIXEL_PATCH
ESC_DCS_IGNORE =256, /* discarding an unknown DCS until ST */
}; };
typedef struct { typedef struct {
@ -2100,6 +2101,7 @@ tmodestatus(int priv, int mode)
#if SYNC_PATCH #if SYNC_PATCH
case 2026: return su ? 1 : 2; case 2026: return su ? 1 : 2;
#endif // SYNC_PATCH #endif // SYNC_PATCH
case 2031: return 4; /* theme change notifications: not supported */
#if SIXEL_PATCH #if SIXEL_PATCH
case 80: return IS_SET(MODE_SIXEL_SDM) ? 1 : 2; case 80: return IS_SET(MODE_SIXEL_SDM) ? 1 : 2;
case 8452: return IS_SET(MODE_SIXEL_CUR_RT) ? 1 : 2; case 8452: return IS_SET(MODE_SIXEL_CUR_RT) ? 1 : 2;
@ -2174,6 +2176,8 @@ tsetmode(int priv, int set, const int *args, int narg)
case 1004: /* 1004: send focus events to tty */ case 1004: /* 1004: send focus events to tty */
xsetmode(set, MODE_FOCUS); xsetmode(set, MODE_FOCUS);
break; break;
case 7727: /* 7727: extended keys (tmux/mintty), not supported */
break;
case 1006: /* 1006: extended reporting mode */ case 1006: /* 1006: extended reporting mode */
xsetmode(set, MODE_MOUSESGR); xsetmode(set, MODE_MOUSESGR);
break; break;
@ -2328,6 +2332,22 @@ csihandle(void)
} }
break; break;
case 'c': /* DA -- Device Attributes */ case 'c': /* DA -- Device Attributes */
if (csiescseq.prefix == '>') {
/*
* DA2 -- Secondary Device Attributes: CSI > Pp ; Pv ; Pc c
* Pp = 0 (VT100 class), Pv = firmware version, Pc = 0.
* tmux and others use this to fingerprint the terminal.
*/
if (csiescseq.arg[0] == 0)
ttywrite("\033[>0;95;0c", 10, 0);
break;
}
if (csiescseq.prefix == '=') {
/* DA3 -- Tertiary Device Attributes: unit id, all zeroes */
if (csiescseq.arg[0] == 0)
ttywrite("\033P!|00000000\033\\", 14, 0);
break;
}
if (csiescseq.arg[0] == 0) if (csiescseq.arg[0] == 0)
ttywrite(vtiden, strlen(vtiden), 0); ttywrite(vtiden, strlen(vtiden), 0);
break; break;
@ -2634,6 +2654,18 @@ csihandle(void)
term.c.y+1, term.c.x+1); term.c.y+1, term.c.x+1);
ttywrite(buffer, len, 0); ttywrite(buffer, len, 0);
break; break;
case 996: /* colour scheme (dark/light) report: CSI ? 997 ; Ps n */
if (csiescseq.priv) {
unsigned char r, g, b;
int dark = 1;
if (!xgetcolor(defaultbg, &r, &g, &b))
dark = (299*r + 587*g + 114*b) / 1000 < 128;
len = snprintf(buffer, sizeof(buffer), "\033[?997;%dn",
dark ? 1 : 2);
ttywrite(buffer, len, 0);
break;
}
goto unknown;
default: default:
goto unknown; goto unknown;
} }
@ -2871,7 +2903,7 @@ strhandle(void)
#endif // CSI_22_23_PATCH #endif // CSI_22_23_PATCH
return; return;
case 52: case 52:
if (narg > 2 && allowwindowops) { if (narg > 2 && (allowwindowops || allowclipboard)) {
dec = base64dec(strescseq.args[2]); dec = base64dec(strescseq.args[2]);
if (dec) { if (dec) {
xsetsel(dec); xsetsel(dec);
@ -3369,9 +3401,13 @@ dcshandle(void)
switch (csiescseq.mode[0]) { switch (csiescseq.mode[0]) {
default: default:
unknown: unknown:
fprintf(stderr, "erresc: unknown csi "); /*
csidump(); * An unknown DCS, e.g. the "DCS tmux;" passthrough wrapper or
/* die(""); */ * XTGETTCAP. Its body must not be interpreted, so discard every
* byte until the terminating ST. Note the body may contain ESC ESC
* (tmux doubles them), which must not end the string.
*/
term.esc |= ESC_DCS_IGNORE;
break; break;
#if SYNC_PATCH #if SYNC_PATCH
case '=': case '=':
@ -3541,6 +3577,23 @@ tputc(Rune u)
* receives a ESC, a SUB, a ST or any other C1 control * receives a ESC, a SUB, a ST or any other C1 control
* character. * character.
*/ */
if (term.esc & ESC_DCS_IGNORE) {
static int sawesc;
if (sawesc) {
sawesc = 0;
if (u == '\\') {
term.esc = 0;
return;
}
/* ESC ESC or ESC <other>: still inside the payload */
if (u != 033)
return;
}
if (u == 033)
sawesc = 1;
return;
}
if (term.esc & ESC_STR) { if (term.esc & ESC_STR) {
if (u == '\a' || u == 030 || u == 032 || u == 033 || if (u == '\a' || u == 030 || u == 032 || u == 033 ||
ISCONTROLC1(u)) { ISCONTROLC1(u)) {

1
st.h
View File

@ -415,6 +415,7 @@ extern wchar_t *kbds_ldelim;
#endif // KEYBOARDSELECT_PATCH #endif // KEYBOARDSELECT_PATCH
extern int allowaltscreen; extern int allowaltscreen;
extern int allowwindowops; extern int allowwindowops;
extern int allowclipboard;
extern char *termname; extern char *termname;
extern unsigned int tabspaces; extern unsigned int tabspaces;
extern unsigned int defaultfg; extern unsigned int defaultfg;

View File

@ -199,11 +199,20 @@ st-mono| simpleterm monocolor,
# rep=%p1%c\E[%p2%{1}%-%db, # rep=%p1%c\E[%p2%{1}%-%db,
# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) # tmux extensions, see TERMINFO EXTENSIONS in tmux(1)
Tc, Tc,
RGB,
Ms=\E]52;%p1%s;%p2%s\007, Ms=\E]52;%p1%s;%p2%s\007,
Se=\E[2 q, Se=\E[2 q,
Ss=\E[%p1%d q, Ss=\E[%p1%d q,
# sync patch / SYNC_PATCH # sync patch / SYNC_PATCH
Sync=\EP=%p1%ds\E\\, Sync=\EP=%p1%ds\E\\,
# undercurl patch / UNDERCURL_PATCH: styled and coloured underlines
Smulx=\E[4:%p1%dm,
Setulc=\E[58:2::%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%dm,
# focus events (mode 1004), used by tmux focus-events
kxIN=\E[I,
kxOUT=\E[O,
# hyperlinks / OSC7_OSC8_PATCH
Hls=\E]8;id=%p1%s;%p2%s\E\\,
st| simpleterm, st| simpleterm,
use=st-mono, use=st-mono,