From 4ee6e01dfc6c0dafeb5e9209161faa5f0b959729 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 13 Jun 2026 09:17:29 +0000 Subject: [PATCH] Add an I format modifier to get some bits of information about a client (terminal features, capabilities and environment). --- usr.bin/tmux/format.c | 52 ++++++++++++++++++++++++++++++++++--- usr.bin/tmux/tmux.1 | 20 +++++++++++++- usr.bin/tmux/tmux.h | 4 ++- usr.bin/tmux/tty-features.c | 41 ++++++++++++++++++++++++++++- usr.bin/tmux/tty-term.c | 14 +++++++++- 5 files changed, 124 insertions(+), 7 deletions(-) diff --git a/usr.bin/tmux/format.c b/usr.bin/tmux/format.c index 62ff151adba..ebb1403bbbc 100644 --- a/usr.bin/tmux/format.c +++ b/usr.bin/tmux/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.375 2026/06/13 08:59:52 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.376 2026/06/13 09:17:29 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -118,6 +118,9 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_REPEAT 0x200000 #define FORMAT_QUOTE_ARGUMENTS 0x400000 #define FORMAT_RELATIVE 0x800000 +#define FORMAT_CLIENT_TERMCAP 0x1000000 +#define FORMAT_CLIENT_TERMFEAT 0x2000000 +#define FORMAT_CLIENT_ENVIRON 0x4000000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -4404,7 +4407,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, /* * Modifiers are a ; separated list of the forms: - * l,m,C,a,b,c,d,n,t,w,q,E,T,S,W,P,R,<,> + * l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,R,<,> * =a * =/a * =/a/ @@ -4445,7 +4448,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, } /* Now try single character with arguments. */ - if (strchr("mCLNPSst=pReqW", cp[0]) == NULL) + if (strchr("ImCLNPSst=pReqW", cp[0]) == NULL) break; c = cp[0]; @@ -5086,6 +5089,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, struct format_modifier *bool_op_n = NULL; u_int i, count, nsub = 0, nrep; struct format_expand_state next; + struct environ_entry *envent; /* Set sorting defaults. */ sc->order = SORT_ORDER; @@ -5168,6 +5172,16 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, case 'n': modifiers |= FORMAT_LENGTH; break; + case 'I': + if (fm->argc < 1) + break; + if (strchr(fm->argv[0], 'f') != NULL) + modifiers |= FORMAT_CLIENT_TERMFEAT; + if (strchr(fm->argv[0], 'c') != NULL) + modifiers |= FORMAT_CLIENT_TERMCAP; + if (strchr(fm->argv[0], 'e') != NULL) + modifiers |= FORMAT_CLIENT_ENVIRON; + break; case 't': modifiers |= FORMAT_TIMESTRING; if (fm->argc < 1) @@ -5295,6 +5309,38 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, } } + /* Look up client capability, feature or environment. */ + if ((modifiers & FORMAT_CLIENT_TERMCAP) || + (modifiers & FORMAT_CLIENT_TERMFEAT) || + (modifiers & FORMAT_CLIENT_ENVIRON)) { + if (ft->c == NULL || + ft->c->tty.term == NULL || + ft->c->flags & CLIENT_UNATTACHEDFLAGS) { + value = xstrdup(""); + goto done; + } + if (modifiers & FORMAT_CLIENT_TERMCAP) { + if (tty_term_has_name(ft->c->tty.term, copy)) + value = xstrdup("1"); + else + value = xstrdup("0"); + } + if (modifiers & FORMAT_CLIENT_TERMFEAT) { + if (tty_feature_present(ft->c->tty.term, copy)) + value = xstrdup("1"); + else + value = xstrdup("0"); + } + if (modifiers & FORMAT_CLIENT_ENVIRON) { + envent = environ_find(ft->c->environ, copy); + if (envent != NULL && envent->value != NULL) + value = xstrdup(envent->value); + else + value = xstrdup(""); + } + goto done; + } + /* Is this a literal string? */ if (modifiers & FORMAT_LITERAL) { format_log(es, "literal string is '%s'", copy); diff --git a/usr.bin/tmux/tmux.1 b/usr.bin/tmux/tmux.1 index cff73a789bb..1434bb4ef5d 100644 --- a/usr.bin/tmux/tmux.1 +++ b/usr.bin/tmux/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1081 2026/06/13 08:59:52 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1082 2026/06/13 09:17:29 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -6429,6 +6429,24 @@ see .Xr strftime 3 . .Pp The +.Ql I:\& +prefix will interrogate the client. +.Ql I/f +will be true if +.Nm +believes that the client supports the given terminal feature, for example +.Ql I/f:RGB , +.Ql I/c +will be true if a client has the given +.Xr terminfo 3 +capability, for example +.Ql I/c:smcup , +and +.Ql I/e +gives the value of a client environment variable, for example +.Ql #{I/e:FOO} . +.Pp +The .Ql b:\& and .Ql d:\& diff --git a/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h index cb9a35020cf..ada35695766 100644 --- a/usr.bin/tmux/tmux.h +++ b/usr.bin/tmux/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1346 2026/06/11 19:13:34 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1347 2026/06/13 09:17:29 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2729,6 +2729,7 @@ int tty_term_read_list(const char *, int, char ***, u_int *, char **); void tty_term_free_list(char **, u_int); int tty_term_has(struct tty_term *, enum tty_code_code); +int tty_term_has_name(struct tty_term *, const char *); const char *tty_term_string(struct tty_term *, enum tty_code_code); const char *tty_term_string_i(struct tty_term *, enum tty_code_code, int); const char *tty_term_string_ii(struct tty_term *, enum tty_code_code, int, @@ -2746,6 +2747,7 @@ const char *tty_term_describe(struct tty_term *, enum tty_code_code); /* tty-features.c */ void tty_add_features(int *, const char *, const char *); const char *tty_get_features(int); +int tty_feature_present(struct tty_term *, const char *); int tty_apply_features(struct tty_term *, int); void tty_default_features(int *, const char *, u_int); diff --git a/usr.bin/tmux/tty-features.c b/usr.bin/tmux/tty-features.c index 01575e7d349..fb8f16a0058 100644 --- a/usr.bin/tmux/tty-features.c +++ b/usr.bin/tmux/tty-features.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-features.c,v 1.36 2026/05/13 10:24:57 nicm Exp $ */ +/* $OpenBSD: tty-features.c,v 1.37 2026/06/13 09:17:29 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -432,6 +432,45 @@ tty_get_features(int feat) return (s); } +int +tty_feature_present(struct tty_term *term, const char *name) +{ + const struct tty_feature *tf = NULL; + const char *const *capability; + u_int i; + char *copy; + + for (i = 0; i < nitems(tty_features); i++) { + tf = tty_features[i]; + if (strcmp(tf->name, name) == 0) { + if (term->features & (1 << i)) + return (1); + break; + } + } + + /* + * We don't just have the feature flag set. Check if the capabilities + * supported by the client are actual set instead. + */ + if (tf == NULL || strcmp(name, "ignorefkeys") == 0) + return (0); + if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags) + return (0); + capability = tf->capabilities; + while (*capability != NULL) { + copy = xstrdup(*capability); + copy[strcspn(copy, "=")] = '\0'; + if (!tty_term_has_name(term, copy)) { + free(copy); + return (0); + } + free(copy); + capability++; + } + return (1); +} + int tty_apply_features(struct tty_term *term, int feat) { diff --git a/usr.bin/tmux/tty-term.c b/usr.bin/tmux/tty-term.c index 9be239d0e61..c8ceb2e5676 100644 --- a/usr.bin/tmux/tty-term.c +++ b/usr.bin/tmux/tty-term.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-term.c,v 1.105 2026/04/22 07:25:17 nicm Exp $ */ +/* $OpenBSD: tty-term.c,v 1.106 2026/06/13 09:17:29 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -765,6 +765,18 @@ tty_term_has(struct tty_term *term, enum tty_code_code code) return (term->codes[code].type != TTYCODE_NONE); } +int +tty_term_has_name(struct tty_term *term, const char *name) +{ + u_int i; + + for (i = 0; i < tty_term_ncodes(); i++) { + if (strcmp(tty_term_codes[i].name, name) == 0) + return (tty_term_has(term, i)); + } + return (0); +} + const char * tty_term_string(struct tty_term *term, enum tty_code_code code) {