patch 9.1.1703: Cannot react to terminal OSC responses

Problem:  Cannot react to terminal OSC responses
Solution: Allow TermResponseAll to be triggered by Terminal OSC
          responses (Foxe Chen)

fixes: #14995
closes: #17975

Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Foxe Chen
2025-08-27 21:15:47 +02:00
committed by Christian Brabandt
parent 7f380259cf
commit 1f51bbc3b9
30 changed files with 404 additions and 303 deletions

View File

@ -116,19 +116,6 @@ static termrequest_T u7_status = TERMREQUEST_INIT;
static termrequest_T xcc_status = TERMREQUEST_INIT;
#ifdef FEAT_TERMRESPONSE
# ifdef FEAT_TERMINAL
// Request foreground color report:
static termrequest_T rfg_status = TERMREQUEST_INIT;
static int fg_r = 0;
static int fg_g = 0;
static int fg_b = 0;
static int bg_r = 255;
static int bg_g = 255;
static int bg_b = 255;
# endif
// Request background color report:
static termrequest_T rbg_status = TERMREQUEST_INIT;
// Request cursor blinking mode report:
static termrequest_T rbm_status = TERMREQUEST_INIT;
@ -143,10 +130,6 @@ static termrequest_T *all_termrequests[] = {
&crv_status,
&u7_status,
&xcc_status,
# ifdef FEAT_TERMINAL
&rfg_status,
# endif
&rbg_status,
&rbm_status,
&rcs_status,
&winpos_status,
@ -4192,49 +4175,6 @@ check_terminal_behavior(void)
}
}
/*
* Similar to requesting the version string: Request the terminal background
* color when it is the right moment.
*/
void
may_req_bg_color(void)
{
if (can_get_termresponse() && starting == 0)
{
int didit = FALSE;
# ifdef FEAT_TERMINAL
// Only request foreground if t_RF is set.
if (rfg_status.tr_progress == STATUS_GET && *T_RFG != NUL)
{
MAY_WANT_TO_LOG_THIS;
LOG_TR1("Sending FG request");
out_str(T_RFG);
termrequest_sent(&rfg_status);
didit = TRUE;
}
# endif
// Only request background if t_RB is set.
if (rbg_status.tr_progress == STATUS_GET && *T_RBG != NUL)
{
MAY_WANT_TO_LOG_THIS;
LOG_TR1("Sending BG request");
out_str(T_RBG);
termrequest_sent(&rbg_status);
didit = TRUE;
}
if (didit)
{
// check for the characters now, otherwise they might be eaten by
// get_keystroke()
out_flush();
(void)vpeekc_nomap();
}
}
}
#endif
/*
@ -5740,105 +5680,89 @@ handle_csi(
return 0;
}
static oscstate_T osc_state;
/*
* Handle an OSC sequence, fore/background color response from the terminal:
*
* {lead}{code};rgb:{rrrr}/{gggg}/{bbbb}{tail}
* or {lead}{code};rgb:{rr}/{gg}/{bb}{tail}
*
* {code} is 10 for foreground, 11 for background
* {lead} can be <Esc>] or OSC
* {tail} can be '\007', <Esc>\ or STERM.
*
* Consume any code that starts with "{lead}11;", it's also
* possible that "rgba" is following.
* Handles any OSC sequence and places the result in "v:termosc". Note that the
* OSC identifier and terminator character(s) will not be placed in the final
* result. Returns OK on success and FAIL on failure.
*/
static int
handle_osc(char_u *tp, char_u *argp, int len, char_u *key_name, int *slen)
handle_osc(char_u *tp, int len, char_u *key_name, int *slen)
{
int i, j;
struct timeval now;
char_u last_char;
j = 1 + (tp[0] == ESC);
if (len >= j + 3 && (argp[0] != '1'
|| (argp[1] != '1' && argp[1] != '0')
|| argp[2] != ';'))
i = 0; // no match
else
for (i = j; i < len; ++i)
if (tp[i] == '\007' || (tp[0] == OSC ? tp[i] == STERM
: (tp[i] == ESC && i + 1 < len && tp[i + 1] == '\\')))
{
int is_bg = argp[1] == '1';
int is_4digit = i - j >= 21 && tp[j + 11] == '/'
&& tp[j + 16] == '/';
if (i - j >= 15 && STRNCMP(tp + j + 3, "rgb:", 4) == 0
&& (is_4digit
|| (tp[j + 9] == '/' && tp[j + 12] == '/')))
{
char_u *tp_r = tp + j + 7;
char_u *tp_g = tp + j + (is_4digit ? 12 : 10);
char_u *tp_b = tp + j + (is_4digit ? 17 : 13);
#if defined(FEAT_TERMRESPONSE) && defined(FEAT_TERMINAL)
int rval, gval, bval;
rval = hexhex2nr(tp_r);
gval = hexhex2nr(tp_g);
bval = hexhex2nr(tp_b);
#endif
if (is_bg)
{
char *new_bg_val = (3 * '6' < *tp_r + *tp_g +
*tp_b) ? "light" : "dark";
LOG_TRN("Received RBG response: %s", tp);
#ifdef FEAT_TERMRESPONSE
rbg_status.tr_progress = STATUS_GOT;
# ifdef FEAT_TERMINAL
bg_r = rval;
bg_g = gval;
bg_b = bval;
# endif
#endif
if (!option_was_set((char_u *)"bg")
&& STRCMP(p_bg, new_bg_val) != 0)
{
// value differs, apply it
set_option_value_give_err((char_u *)"bg",
0L, (char_u *)new_bg_val, 0);
reset_option_was_set((char_u *)"bg");
redraw_asap(UPD_CLEAR);
}
}
#if defined(FEAT_TERMRESPONSE) && defined(FEAT_TERMINAL)
else
{
LOG_TRN("Received RFG response: %s", tp);
rfg_status.tr_progress = STATUS_GOT;
fg_r = rval;
fg_g = gval;
fg_b = bval;
}
#endif
}
// got finished code: consume it
key_name[0] = (int)KS_EXTRA;
key_name[1] = (int)KE_IGNORE;
*slen = i + 1 + (tp[i] == ESC);
#ifdef FEAT_EVAL
set_vim_var_string(is_bg ? VV_TERMRBGRESP
: VV_TERMRFGRESP, tp, *slen);
#endif
apply_autocmds(EVENT_TERMRESPONSEALL,
is_bg ? (char_u *)"background" : (char_u *)"foreground", NULL, FALSE, curbuf);
break;
}
if (i == len)
if (!osc_state.processing)
{
LOG_TR1("not enough characters for RB");
int cur;
LOG_TRN("Received OSC response: %s", (char*)tp);
// Check if it is a valid OSC sequence, and consume it. OSC format
// consists of:
// <idenfitifer><data><terminator>
// <identifier> is either <Esc>] or an OSC character
// <terminator> can be '\007', <Esc>\ or STERM.
cur = 1 + (tp[0] == ESC);
if (len < cur + 1 + (tp[0] != OSC)) // Include terminator as well
return FAIL;
// The whole OSC response may be larger than the typeahead buffer.
// To handle this, keep reading data in and out of the typeahead
// buffer until we read an OSC terminator or timeout.
ga_init2(&osc_state.buf, 1, 1024);
gettimeofday(&osc_state.start, NULL);
osc_state.processing = TRUE;
osc_state.start_char = tp[0];
last_char = 0;
}
else
last_char = ((char_u *)osc_state.buf.ga_data)[osc_state.buf.ga_len - 1];
key_name[0] = (int)KS_EXTRA;
key_name[1] = (int)KE_IGNORE;
// Read data and append to buffer. If we reach a terminator, then
// finally set the vim var.
for (int i = 0; i < len; i++)
if (tp[i] == '\007' || (osc_state.start_char == OSC ? tp[i] == STERM
: ((tp[i] == ESC && i + 1 < len && tp[i + 1] == '\\')
|| (i == 0 && tp[i] == '\\' && last_char == ESC)
)))
{
osc_state.processing = FALSE;
ga_concat_len(&osc_state.buf, tp, i + 1 + (tp[i] == ESC));
ga_append(&osc_state.buf, NUL);
*slen = i + 1 + (tp[i] == ESC);
#ifdef FEAT_EVAL
set_vim_var_string_direct(VV_TERMOSC, osc_state.buf.ga_data);
#endif
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"osc",
NULL, FALSE, curbuf);
return OK;
}
// Check if timeout has been reached
gettimeofday(&now, NULL);
if ((now.tv_sec * 1000000 + now.tv_usec) -
(osc_state.start.tv_sec * 1000000 + osc_state.start.tv_usec)
>= p_ost * 1000)
{
semsg(_(e_osc_response_timed_out), osc_state.buf.ga_len,
osc_state.buf.ga_data);
ga_clear(&osc_state.buf);
osc_state.processing = FALSE;
return FAIL;
}
ga_concat(&osc_state.buf, tp);
*slen = len; // Consume everything
return OK;
}
@ -6028,6 +5952,11 @@ check_termcode(
continue;
}
if (osc_state.processing)
// Still processing OSC response data, go straight to handler
// function.
goto handle_osc;
/*
* Skip this position if the character does not appear as the first
* character in term_strings. This speeds up a lot, since most
@ -6300,13 +6229,11 @@ check_termcode(
}
}
// Check for fore/background color response from the terminal,
// starting} with <Esc>] or OSC
else if ((*T_RBG != NUL || *T_RFG != NUL)
&& ((tp[0] == ESC && len >= 2 && tp[1] == ']')
|| tp[0] == OSC))
// Check for OSC responses from terminal
else if ((tp[0] == ESC && len >= 2 && tp[1] == ']') || tp[0] == OSC)
{
if (handle_osc(tp, argp, len, key_name, &slen) == FAIL)
handle_osc:
if (handle_osc(tp, len, key_name, &slen) == FAIL)
return -1;
}
@ -6549,10 +6476,12 @@ check_termcode(
*/
key = handle_x_keys(TERMCAP2KEY(key_name[0], key_name[1]));
/*
* Add any modifier codes to our string.
*/
new_slen = modifiers2keycode(modifiers, &key, string);
if (osc_state.processing)
// We don't want to add anything to the typeahead buffer.
new_slen = 0;
else
// Add any modifier codes to our string.
new_slen = modifiers2keycode(modifiers, &key, string);
// Finally, add the special key code to our string
key_name[0] = KEY2TERMCAP0(key);
@ -6592,18 +6521,28 @@ check_termcode(
}
#if (defined(FEAT_TERMINAL) && defined(FEAT_TERMRESPONSE)) || defined(PROTO)
static void
term_get_color(char_u *str, char_u *r, char_u *g, char_u *b)
{
char_u rn[3], gn[3], bn[3];
if (sscanf((char *)str, "%*[^:]:%2s%*[^/]/%2s%*[^/]/%2s",
(char *)&rn, (char *)&gn, (char *)&bn) != 3)
return;
*r = hexhex2nr(rn);
*g = hexhex2nr(gn);
*b = hexhex2nr(bn);
}
/*
* Get the text foreground color, if known.
*/
void
term_get_fg_color(char_u *r, char_u *g, char_u *b)
{
if (rfg_status.tr_progress != STATUS_GOT)
return;
*r = fg_r;
*g = fg_g;
*b = fg_b;
term_get_color(get_vim_var_str(VV_TERMRFGRESP), r, g, b);
}
/*
@ -6612,12 +6551,7 @@ term_get_fg_color(char_u *r, char_u *g, char_u *b)
void
term_get_bg_color(char_u *r, char_u *g, char_u *b)
{
if (rbg_status.tr_progress != STATUS_GOT)
return;
*r = bg_r;
*g = bg_g;
*b = bg_b;
term_get_color(get_vim_var_str(VV_TERMRBGRESP), r, g, b);
}
#endif