Tidy up the terminal detection and feature code and add named sets of

terminal features, each of which are defined in one place and map to a
builtin set of terminfo(5) capabilities. Features can be specified based
on TERM with a new terminal-features option or with the -T flag when
running tmux. tmux will also detect a few common terminals from the DA
and DSR responses.

This is intended to make it easier to configure tmux's use of
terminfo(5) even in the presence of outdated ncurses(3) or terminfo(5)
databases or for features which do not yet have a terminfo(5) entry.
Instead of having to grok terminfo(5) capability names and what they
should be set to in the terminal-overrides option, the user can
hopefully just give tmux a feature name and let it do the right thing.

The terminal-overrides option remains both for backwards compatibility
and to allow tweaks of individual capabilities.

tmux already did much of this already, this makes it tidier and simpler
to configure.
This commit is contained in:
nicm 2020-04-20 13:25:36 +00:00
parent 86862c976a
commit c91b4b2e14
14 changed files with 540 additions and 223 deletions

View File

@ -111,6 +111,7 @@ SRCS= alerts.c \
style.c \
tmux.c \
tty-acs.c \
tty-features.c \
tty-keys.c \
tty-term.c \
tty.c \

View File

@ -57,7 +57,7 @@ static struct client_files client_files = RB_INITIALIZER(&client_files);
static __dead void client_exec(const char *,const char *);
static int client_get_lock(char *);
static int client_connect(struct event_base *, const char *, int);
static void client_send_identify(const char *, const char *);
static void client_send_identify(const char *, const char *, int);
static void client_signal(int);
static void client_dispatch(struct imsg *, void *);
static void client_dispatch_attached(struct imsg *);
@ -233,7 +233,7 @@ client_exit(void)
/* Client main loop. */
int
client_main(struct event_base *base, int argc, char **argv, int flags)
client_main(struct event_base *base, int argc, char **argv, int flags, int feat)
{
struct cmd_parse_result *pr;
struct msg_command *data;
@ -340,7 +340,7 @@ client_main(struct event_base *base, int argc, char **argv, int flags)
}
/* Send identify messages. */
client_send_identify(ttynam, cwd);
client_send_identify(ttynam, cwd, feat);
/* Send first command. */
if (msg == MSG_COMMAND) {
@ -406,7 +406,7 @@ client_main(struct event_base *base, int argc, char **argv, int flags)
/* Send identify messages to server. */
static void
client_send_identify(const char *ttynam, const char *cwd)
client_send_identify(const char *ttynam, const char *cwd, int feat)
{
const char *s;
char **ss;
@ -419,6 +419,7 @@ client_send_identify(const char *ttynam, const char *cwd)
if ((s = getenv("TERM")) == NULL)
s = "";
proc_send(client_peer, MSG_IDENTIFY_TERM, -1, s, strlen(s) + 1);
proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat);
proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam,
strlen(ttynam) + 1);

View File

@ -43,22 +43,24 @@ const struct cmd_entry cmd_show_messages_entry = {
.exec = cmd_show_messages_exec
};
static int cmd_show_messages_terminals(struct cmdq_item *, int);
static int
cmd_show_messages_terminals(struct cmdq_item *item, int blank)
cmd_show_messages_terminals(struct cmd *self, struct cmdq_item *item, int blank)
{
struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item);
struct tty_term *term;
u_int i, n;
n = 0;
LIST_FOREACH(term, &tty_terms, entry) {
if (args_has(args, 't') && term != tc->tty.term)
continue;
if (blank) {
cmdq_print(item, "%s", "");
blank = 0;
}
cmdq_print(item, "Terminal %u: %s [references=%u, flags=0x%x]:",
n, term->name, term->references, term->flags);
cmdq_print(item, "Terminal %u: %s for %s, flags=0x%x:", n,
term->name, term->tty->client->name, term->flags);
n++;
for (i = 0; i < tty_term_ncodes(); i++)
cmdq_print(item, "%s", tty_term_describe(term, i));
@ -77,7 +79,7 @@ cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item)
done = blank = 0;
if (args_has(args, 'T')) {
blank = cmd_show_messages_terminals(item, blank);
blank = cmd_show_messages_terminals(self, item, blank);
done = 1;
}
if (args_has(args, 'J')) {

View File

@ -2553,8 +2553,9 @@ format_defaults_client(struct format_tree *ft, struct client *c)
format_add(ft, "client_control_mode", "%d",
!!(c->flags & CLIENT_CONTROL));
if (tty->term_name != NULL)
format_add(ft, "client_termname", "%s", tty->term_name);
format_add(ft, "client_termname", "%s", c->term_name);
format_add(ft, "client_termfeatures", "%s",
tty_get_features(c->term_features));
format_add_tv(ft, "client_created", &c->creation_time);
format_add_tv(ft, "client_activity", &c->activity_time);
@ -2569,7 +2570,7 @@ format_defaults_client(struct format_tree *ft, struct client *c)
format_add(ft, "client_prefix", "%d", 1);
format_add(ft, "client_key_table", "%s", c->keytable->name);
if (tty_get_flags(tty) & TERM_UTF8)
if (c->flags & CLIENT_UTF8)
format_add(ft, "client_utf8", "%d", 1);
else
format_add(ft, "client_utf8", "%d", 0);

View File

@ -261,9 +261,16 @@ const struct options_table_entry options_table[] = {
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_ARRAY,
.default_str = "xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007"
":Cs=\\E]12;%p1%s\\007:Cr=\\E]112\\007"
":Ss=\\E[%p1%d q:Se=\\E[2 q,screen*:XT",
.default_str = "tmux*:XT,screen*:XT",
.separator = ","
},
{ .name = "terminal-features",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_ARRAY,
.default_str = "xterm*:clipboard:ccolour:cstyle:title,"
"screen*:title",
.separator = ","
},

View File

@ -296,7 +296,7 @@ server_client_lost(struct client *c)
if (c->flags & CLIENT_TERMINAL)
tty_free(&c->tty);
free(c->ttyname);
free(c->term);
free(c->term_name);
status_free(c);
@ -1845,6 +1845,7 @@ server_client_dispatch(struct imsg *imsg, void *arg)
datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
switch (imsg->hdr.type) {
case MSG_IDENTIFY_FEATURES:
case MSG_IDENTIFY_FLAGS:
case MSG_IDENTIFY_TERM:
case MSG_IDENTIFY_TTYNAME:
@ -2003,7 +2004,7 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
{
const char *data, *home;
size_t datalen;
int flags;
int flags, feat;
char *name;
if (c->flags & CLIENT_IDENTIFIED)
@ -2013,6 +2014,14 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
switch (imsg->hdr.type) {
case MSG_IDENTIFY_FEATURES:
if (datalen != sizeof feat)
fatalx("bad MSG_IDENTIFY_FEATURES size");
memcpy(&feat, data, sizeof feat);
c->term_features |= feat;
log_debug("client %p IDENTIFY_FEATURES %s", c,
tty_get_features(feat));
break;
case MSG_IDENTIFY_FLAGS:
if (datalen != sizeof flags)
fatalx("bad MSG_IDENTIFY_FLAGS size");
@ -2023,7 +2032,10 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
case MSG_IDENTIFY_TERM:
if (datalen == 0 || data[datalen - 1] != '\0')
fatalx("bad MSG_IDENTIFY_TERM string");
c->term = xstrdup(data);
if (*data == '\0')
c->term_name = xstrdup("unknown");
else
c->term_name = xstrdup(data);
log_debug("client %p IDENTIFY_TERM %s", c, data);
break;
case MSG_IDENTIFY_TTYNAME:
@ -2084,14 +2096,10 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
control_start(c);
c->tty.fd = -1;
} else if (c->fd != -1) {
if (tty_init(&c->tty, c, c->fd, c->term) != 0) {
if (tty_init(&c->tty, c, c->fd) != 0) {
close(c->fd);
c->fd = -1;
} else {
if (c->flags & CLIENT_UTF8)
c->tty.term_flags |= TERM_UTF8;
if (c->flags & CLIENT_256COLOURS)
c->tty.term_flags |= TERM_256COLOURS;
tty_resize(&c->tty);
c->flags |= CLIENT_TERMINAL;
}

92
tmux.1
View File

@ -28,6 +28,7 @@
.Op Fl f Ar file
.Op Fl L Ar socket-name
.Op Fl S Ar socket-path
.Op Fl T Ar features
.Op Ar command Op Ar flags
.Ek
.Sh DESCRIPTION
@ -98,6 +99,8 @@ The options are as follows:
Force
.Nm
to assume the terminal supports 256 colours.
This is equivalent to
.Fl T Ar 256 .
.It Fl C
Start in control mode (see the
.Sx CONTROL MODE
@ -186,6 +189,14 @@ that is set does not contain
.Qq UTF-8
or
.Qq UTF8 .
This is equivalent to
.Fl T Ar UTF-8 .
.It Fl T Ar features
Set terminal features for the client.
This is a comma-separated list of features.
See the
.Ic terminal-features
option.
.It Fl v
Request verbose logging.
Log messages will be saved into
@ -3166,6 +3177,63 @@ disallowedWindowOps: 20,21,SetXprop
Or changing this property from the
.Xr xterm 1
interactive menu when required.
.It Ic terminal-features[] Ar string
Set terminal features for terminal types read from
.Xr terminfo 5 .
.Nm
has a set of named terminal features.
Each will apply appropriate changes to the
.Xr terminfo 5
entry in use.
.Pp
.Nm
can detect features for a few common terminals; this option can be used to
easily tell tmux about features supported by terminals it cannot detect.
The
.Ic terminal-overrides
option allows individual
.Xr terminfo 5
capabilities to be set instead,
.Ic terminal-features
is intended for classes of functionality supported in a standard way but not
reported by
.Xr terminfo 5 .
Care must be taken only to configure this with features the terminal actually
support.
.Pp
This is an array option where each entry is a colon-separated string made up
of a terminal type pattern (matched using
.Xr fnmatch 3 )
followed by a list of terminal features.
The available features are:
.Bl -tag -width Ds
.It 256
Supports 256 colours with the SGR escape sequences.
.It clipboard
Allows setting the system clipboard.
.It ccolour
Allows setting the cursor colour.
.It cstyle
Allows setting the cursor style.
.It margins
Supports DECSLRM margins.
.It overline
Supports the overline SGR attribute.
.It rectfill
Supports the DECFRA rectangle fill escape sequence.
.It RGB
Supports RGB colour with the SGR escape sequences.
.It sync
Supports synchronized updates.
.It title
Supports
.Xr xterm 1
title setting.
.It usstyle
Allows underscore style and colour to be set.
.It UTF-8
Is able to handle UTF-8 output.
.El
.It Ic terminal-overrides[] Ar string
Allow terminal descriptions read using
.Xr terminfo 5
@ -4383,6 +4451,7 @@ The following variables are available, where appropriate:
.It Li "client_readonly" Ta "" Ta "1 if client is readonly"
.It Li "client_session" Ta "" Ta "Name of the client's session"
.It Li "client_termname" Ta "" Ta "Terminal name of client"
.It Li "client_termfeatures" Ta "" Ta "Terminal features of client"
.It Li "client_tty" Ta "" Ta "Pseudo terminal of client"
.It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8"
.It Li "client_width" Ta "" Ta "Width of client"
@ -5465,7 +5534,10 @@ The server crashed or otherwise exited without telling the client the reason.
.Sh TERMINFO EXTENSIONS
.Nm
understands some unofficial extensions to
.Xr terminfo 5 :
.Xr terminfo 5.
It is not normally necessary to set these manually, instead the
.Ic terminal-features
option should be used.
.Bl -tag -width Ds
.It Em \&Cs , Cr
Set the cursor colour.
@ -5479,33 +5551,15 @@ $ printf '\e033]12;red\e033\e\e'
.Ed
.It Em \&Smol
Enable the overline attribute.
The capability is usually SGR 53 and can be added to
.Ic terminal-overrides
as:
.Bd -literal -offset indent
Smol=\eE[53m
.Ed
.It Em \&Smulx
Set a styled underscore.
The single parameter is one of: 0 for no underscore, 1 for normal
underscore, 2 for double underscore, 3 for curly underscore, 4 for dotted
underscore and 5 for dashed underscore.
The capability can typically be added to
.Ic terminal-overrides
as:
.Bd -literal -offset indent
Smulx=\eE[4::%p1%dm
.Ed
.It Em \&Setulc
Set the underscore colour.
The argument is (red * 65536) + (green * 256) + blue where each is between 0
and 255.
The capability can typically be added to
.Ic terminal-overrides
as:
.Bd -literal -offset indent
Setulc=\eE[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m
.Ed
.It Em \&Ss , Se
Set or reset the cursor style.
If set, a sequence such as this may be used

20
tmux.c
View File

@ -58,7 +58,7 @@ usage(void)
{
fprintf(stderr,
"usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n"
" [-S socket-path] [command [flags]]\n",
" [-S socket-path] [-T features] [command [flags]]\n",
getprogname());
exit(1);
}
@ -242,9 +242,11 @@ getversion(void)
int
main(int argc, char **argv)
{
char *path, *label, *cause, **var;
char *path = NULL, *label = NULL;
char *cause, **var;
const char *s, *shell, *cwd;
int opt, flags, keys;
int opt, flags = 0, keys;
int feat = 0;
const struct options_table_entry *oe;
if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
@ -261,14 +263,11 @@ main(int argc, char **argv)
if (**argv == '-')
flags = CLIENT_LOGIN;
else
flags = 0;
label = path = NULL;
while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUvV")) != -1) {
while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:T:uUvV")) != -1) {
switch (opt) {
case '2':
flags |= CLIENT_256COLOURS;
tty_add_features(&feat, "256", ":,");
break;
case 'c':
shell_command = optarg;
@ -298,6 +297,9 @@ main(int argc, char **argv)
free(path);
path = xstrdup(optarg);
break;
case 'T':
tty_add_features(&feat, optarg, ":,");
break;
case 'u':
flags |= CLIENT_UTF8;
break;
@ -405,5 +407,5 @@ main(int argc, char **argv)
free(label);
/* Pass control to the client. */
exit(client_main(event_init(), argc, argv, flags));
exit(client_main(event_init(), argc, argv, flags, feat));
}

30
tmux.h
View File

@ -474,6 +474,7 @@ enum msgtype {
MSG_IDENTIFY_DONE,
MSG_IDENTIFY_CLIENTPID,
MSG_IDENTIFY_CWD,
MSG_IDENTIFY_FEATURES,
MSG_COMMAND = 200,
MSG_DETACH,
@ -1176,7 +1177,8 @@ struct tty_key {
struct tty_code;
struct tty_term {
char *name;
u_int references;
struct tty *tty;
int features;
char acs[UCHAR_MAX + 1][2];
@ -1187,8 +1189,6 @@ struct tty_term {
#define TERM_DECSLRM 0x4
#define TERM_DECFRA 0x8
#define TERM_RGBCOLOURS 0x10
#define TERM_SYNC 0x20
#define TERM_UTF8 0x40
int flags;
LIST_ENTRY(tty_term) entry;
@ -1252,8 +1252,6 @@ struct tty {
int flags;
struct tty_term *term;
char *term_name;
int term_flags;
u_int mouse_last_x;
u_int mouse_last_y;
@ -1267,7 +1265,6 @@ struct tty {
struct event key_timer;
struct tty_key *key_tree;
};
#define tty_term_flags(tty) (tty->term->flags|tty->term_flags)
/* TTY command context. */
struct tty_ctx {
@ -1498,7 +1495,9 @@ struct client {
char *title;
const char *cwd;
char *term;
char *term_name;
int term_features;
char *ttyname;
struct tty tty;
@ -1531,7 +1530,7 @@ struct client {
#define CLIENT_CONTROLCONTROL 0x4000
#define CLIENT_FOCUSED 0x8000
#define CLIENT_UTF8 0x10000
#define CLIENT_256COLOURS 0x20000
/* 0x20000 unused */
#define CLIENT_IDENTIFIED 0x40000
#define CLIENT_STATUSFORCE 0x80000
#define CLIENT_DOUBLECLICK 0x100000
@ -1953,7 +1952,7 @@ void tty_putcode_ptr2(struct tty *, enum tty_code_code, const void *,
void tty_puts(struct tty *, const char *);
void tty_putc(struct tty *, u_char);
void tty_putn(struct tty *, const void *, size_t, u_int);
int tty_init(struct tty *, struct client *, int, char *);
int tty_init(struct tty *, struct client *, int);
void tty_resize(struct tty *);
void tty_set_size(struct tty *, u_int, u_int, u_int, u_int);
void tty_start_tty(struct tty *);
@ -1968,8 +1967,7 @@ void tty_sync_end(struct tty *);
int tty_open(struct tty *, char **);
void tty_close(struct tty *);
void tty_free(struct tty *);
void tty_set_flags(struct tty *, int);
int tty_get_flags(struct tty *);
void tty_update_features(struct tty *);
void tty_write(void (*)(struct tty *, const struct tty_ctx *),
struct tty_ctx *);
void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *);
@ -1999,7 +1997,8 @@ void tty_cmd_syncend(struct tty *, const struct tty_ctx *);
/* tty-term.c */
extern struct tty_terms tty_terms;
u_int tty_term_ncodes(void);
struct tty_term *tty_term_find(char *, int, char **);
void tty_term_apply(struct tty_term *, const char *, int);
struct tty_term *tty_term_create(struct tty *, char *, int *, int, char **);
void tty_term_free(struct tty_term *);
int tty_term_has(struct tty_term *, enum tty_code_code);
const char *tty_term_string(struct tty_term *, enum tty_code_code);
@ -2016,6 +2015,11 @@ int tty_term_number(struct tty_term *, enum tty_code_code);
int tty_term_flag(struct tty_term *, enum tty_code_code);
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);
void tty_apply_features(struct tty_term *, int);
/* tty-acs.c */
int tty_acs_needed(struct tty *);
const char *tty_acs_get(struct tty *, u_char);
@ -2161,7 +2165,7 @@ void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...);
void cmd_wait_for_flush(void);
/* client.c */
int client_main(struct event_base *, int, char **, int);
int client_main(struct event_base *, int, char **, int, int);
/* key-bindings.c */
struct key_table *key_bindings_get_table(const char *, int);

View File

@ -99,7 +99,7 @@ tty_acs_needed(struct tty *tty)
tty_term_number(tty->term, TTYC_U8) == 0)
return (1);
if (tty_get_flags(tty) & TERM_UTF8)
if (tty->client->flags & CLIENT_UTF8)
return (0);
return (1);
}

264
tty-features.c Normal file
View File

@ -0,0 +1,264 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* Still hardcoded:
* - bracket paste (sent if application asks for it);
* - mouse (under kmous capability);
* - focus events (under focus-events option);
* - default colours (under AX or op capabilities);
* - AIX colours (under colors >= 16);
* - alternate escape (under XT).
*
* Also:
* - XT is used to decide whether to send DA and DSR,
* - DECSLRM and DECFRA use a flag instead of capabilities.
* - Sync is a flag rather than a string capability.
* - UTF-8 is a separate flag on the client; needed for unattached clients.
*/
/* A named terminal feature. */
struct tty_feature {
const char *name;
const char **capabilities;
int flags;
};
/* Terminal has xterm(1) title setting. */
static const char *tty_feature_title_capabilities[] = {
"tsl=\\E]0;", /* should be using TS really */
"fsl=\\a",
NULL
};
static struct tty_feature tty_feature_title = {
"title",
tty_feature_title_capabilities,
0
};
/* Terminal can set the clipboard with OSC 52. */
static const char *tty_feature_clipboard_capabilities[] = {
"Ms=\\E]52;%p1%s;%p2%s\\a",
NULL
};
static struct tty_feature tty_feature_clipboard = {
"clipboard",
tty_feature_clipboard_capabilities,
0
};
/*
* Terminal supports RGB colour. This replaces setab and setaf also since
* terminals with RGB have versions that do not allow setting colours from the
* 256 palette.
*/
static const char *tty_feature_rgb_capabilities[] = {
"setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm",
"setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm",
"setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
"setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
NULL
};
static struct tty_feature tty_feature_rgb = {
"RGB",
tty_feature_rgb_capabilities,
(TERM_256COLOURS|TERM_RGBCOLOURS)
};
/* Terminal supports 256 colours. */
static const char *tty_feature_256_capabilities[] = {
"setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
"setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
NULL
};
static struct tty_feature tty_feature_256 = {
"256",
tty_feature_256_capabilities,
TERM_256COLOURS
};
/* Terminal supports overline. */
static const char *tty_feature_overline_capabilities[] = {
"Smol=\\E[53m",
NULL
};
static struct tty_feature tty_feature_overline = {
"overline",
tty_feature_overline_capabilities,
0
};
/* Terminal supports underscore styles. */
static const char *tty_feature_usstyle_capabilities[] = {
"Smulx=\E[4::%p1%dm",
"Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m",
NULL
};
static struct tty_feature tty_feature_usstyle = {
"usstyle",
tty_feature_usstyle_capabilities,
0
};
/* Terminal supports cursor styles. */
static const char *tty_feature_cstyle_capabilities[] = {
"Ss=\\E[%p1%d q",
"Se=\\E[2 q",
NULL
};
static struct tty_feature tty_feature_cstyle = {
"cstyle",
tty_feature_cstyle_capabilities,
0
};
/* Terminal supports cursor colours. */
static const char *tty_feature_ccolour_capabilities[] = {
"Cs=\\E]12;%p1%s\\a",
"Cr=\\E]112\\a",
NULL
};
static struct tty_feature tty_feature_ccolour = {
"ccolour",
tty_feature_ccolour_capabilities,
0
};
/* Terminal supports synchronized updates. */
static const char *tty_feature_sync_capabilities[] = {
"Sync",
NULL
};
static struct tty_feature tty_feature_sync = {
"sync",
tty_feature_sync_capabilities,
0
};
/* Terminal supports DECSLRM margins. */
static struct tty_feature tty_feature_margins = {
"margins",
NULL,
TERM_DECSLRM
};
/* Terminal supports DECFRA rectangle fill. */
static struct tty_feature tty_feature_rectfill = {
"rectfill",
NULL,
TERM_DECFRA
};
/* Available terminal features. */
static const struct tty_feature *tty_features[] = {
&tty_feature_256,
&tty_feature_clipboard,
&tty_feature_ccolour,
&tty_feature_cstyle,
&tty_feature_margins,
&tty_feature_overline,
&tty_feature_rectfill,
&tty_feature_rgb,
&tty_feature_sync,
&tty_feature_title,
&tty_feature_usstyle
};
void
tty_add_features(int *feat, const char *s, const char *separators)
{
const struct tty_feature *tf;
char *next, *loop, *copy;
u_int i;
loop = copy = xstrdup(s);
while ((next = strsep(&loop, separators)) != NULL) {
for (i = 0; i < nitems(tty_features); i++) {
tf = tty_features[i];
if (strcasecmp(tf->name, next) == 0)
break;
}
if (i == nitems(tty_features)) {
log_debug("unknown terminal feature: %s", next);
break;
}
if (~(*feat) & (1 << i)) {
log_debug("adding terminal feature: %s", tf->name);
(*feat) |= (1 << i);
}
}
free(copy);
}
const char *
tty_get_features(int feat)
{
const struct tty_feature *tf;
static char s[512];
u_int i;
*s = '\0';
for (i = 0; i < nitems(tty_features); i++) {
if (~feat & (1 << i))
continue;
tf = tty_features[i];
strlcat(s, tf->name, sizeof s);
strlcat(s, ",", sizeof s);
}
if (*s != '\0')
s[strlen(s) - 1] = '\0';
return (s);
}
void
tty_apply_features(struct tty_term *term, int feat)
{
const struct tty_feature *tf;
const char **capability;
u_int i;
if (feat == 0)
return;
log_debug("applying terminal features: %s", tty_get_features(feat));
for (i = 0; i < nitems(tty_features); i++) {
if ((term->features & (1 << i)) || (~feat & (1 << i)))
continue;
tf = tty_features[i];
log_debug("applying terminal feature: %s", tf->name);
if (tf->capabilities != NULL) {
capability = tf->capabilities;
while (*capability != NULL) {
log_debug("adding capability: %s", *capability);
tty_term_apply(term, *capability, 1);
capability++;
}
}
term->flags |= tf->flags;
}
term->features |= feat;
}

View File

@ -1020,7 +1020,6 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
struct client *c = tty->client;
u_int i, n = 0;
char tmp[64], *endptr, p[32] = { 0 }, *cp, *next;
int flags = 0;
*size = 0;
if (tty->flags & TTY_HAVEDA)
@ -1060,24 +1059,42 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
n++;
}
/* Set terminal flags. */
/* Add terminal features. */
switch (p[0]) {
case 41: /* VT420 */
flags |= (TERM_DECFRA|TERM_DECSLRM);
tty_add_features(&c->term_features,
"margins,"
"rectfill",
",");
break;
case 'M': /* mintty */
flags |= (TERM_256COLOURS|TERM_RGBCOLOURS);
tty_add_features(&c->term_features,
"256,"
"RGB,"
"title",
",");
break;
case 'T': /* tmux - new versons reply to DSR which will set RGB */
flags |= (TERM_UTF8|TERM_256COLOURS);
case 'T': /* tmux */
tty_add_features(&c->term_features,
"256,"
"RGB,"
"ccolour,"
"cstyle,"
"overline,"
"title,"
"usstyle",
",");
break;
case 'U': /* rxvt-unicode */
flags |= (TERM_UTF8);
tty_add_features(&c->term_features,
"256,"
"title",
",");
break;
}
log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf);
tty_set_flags(tty, flags);
tty_update_features(tty);
tty->flags |= TTY_HAVEDA;
return (0);
@ -1094,7 +1111,6 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len,
struct client *c = tty->client;
u_int i;
char tmp[64];
int flags = 0;
*size = 0;
if (tty->flags & TTY_HAVEDSR)
@ -1125,15 +1141,31 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len,
tmp[i] = '\0';
*size = 3 + i;
/* Set terminal flags. */
/* Add terminal features. */
if (strncmp(tmp, "ITERM2 ", 7) == 0) {
flags |= (TERM_UTF8|TERM_DECSLRM|TERM_SYNC|TERM_256COLOURS|
TERM_RGBCOLOURS);
} else if (strncmp(tmp, "TMUX ", 5) == 0)
flags |= (TERM_UTF8|TERM_256COLOURS|TERM_RGBCOLOURS);
tty_add_features(&c->term_features,
"256,"
"RGB,"
"clipboard,"
"cstyle,"
"margins,"
"sync,"
"title,",
",");
} else if (strncmp(tmp, "TMUX ", 5) == 0) {
tty_add_features(&c->term_features,
"256,"
"RGB,"
"ccolour,"
"cstyle,"
"overline,"
"title,"
"usstyle",
",");
}
log_debug("%s: received DSR %.*s", c->name, (int)*size, buf);
tty_set_flags(tty, flags);
tty_update_features(tty);
tty->flags |= TTY_HAVEDSR;
return (0);

View File

@ -27,7 +27,6 @@
#include "tmux.h"
static void tty_term_override(struct tty_term *, const char *);
static char *tty_term_strip(const char *);
struct tty_terms tty_terms = LIST_HEAD_INITIALIZER(tty_terms);
@ -335,22 +334,18 @@ tty_term_override_next(const char *s, size_t *offset)
return (value);
}
static void
tty_term_override(struct tty_term *term, const char *override)
void
tty_term_apply(struct tty_term *term, const char *capabilities, int quiet)
{
const struct tty_term_code_entry *ent;
struct tty_code *code;
size_t offset = 0;
char *cp, *value, *s;
const char *errstr;
const char *errstr, *name = term->name;
u_int i;
int n, remove;
s = tty_term_override_next(override, &offset);
if (s == NULL || fnmatch(s, term->name, 0) != 0)
return;
while ((s = tty_term_override_next(override, &offset)) != NULL) {
while ((s = tty_term_override_next(capabilities, &offset)) != NULL) {
if (*s == '\0')
continue;
value = NULL;
@ -369,12 +364,14 @@ tty_term_override(struct tty_term *term, const char *override)
} else
value = xstrdup("");
if (remove)
log_debug("%s override: %s@", term->name, s);
else if (*value == '\0')
log_debug("%s override: %s", term->name, s);
else
log_debug("%s override: %s=%s", term->name, s, value);
if (!quiet) {
if (remove)
log_debug("%s override: %s@", name, s);
else if (*value == '\0')
log_debug("%s override: %s", name, s);
else
log_debug("%s override: %s=%s", name, s, value);
}
for (i = 0; i < tty_term_ncodes(); i++) {
ent = &tty_term_codes[i];
@ -414,7 +411,7 @@ tty_term_override(struct tty_term *term, const char *override)
}
struct tty_term *
tty_term_find(char *name, int fd, char **cause)
tty_term_create(struct tty *tty, char *name, int *feat, int fd, char **cause)
{
struct tty_term *term;
const struct tty_term_code_entry *ent;
@ -425,19 +422,14 @@ tty_term_find(char *name, int fd, char **cause)
u_int i;
int n, error;
const char *s, *acs;
size_t offset;
char *first;
LIST_FOREACH(term, &tty_terms, entry) {
if (strcmp(term->name, name) == 0) {
term->references++;
return (term);
}
}
log_debug("new term: %s", name);
log_debug("adding term %s", name);
term = xmalloc(sizeof *term);
term = xcalloc(1, sizeof *term);
term->tty = tty;
term->name = xstrdup(name);
term->references = 1;
term->flags = 0;
term->codes = xcalloc(tty_term_ncodes(), sizeof *term->codes);
LIST_INSERT_HEAD(&tty_terms, term, entry);
@ -495,12 +487,31 @@ tty_term_find(char *name, int fd, char **cause)
}
}
/* Apply terminal features. */
o = options_get_only(global_options, "terminal-features");
a = options_array_first(o);
while (a != NULL) {
ov = options_array_item_value(a);
s = ov->string;
offset = 0;
first = tty_term_override_next(s, &offset);
if (first != NULL && fnmatch(first, term->name, 0) == 0)
tty_add_features(feat, s + offset, ":");
a = options_array_next(a);
}
/* Apply terminal overrides. */
o = options_get_only(global_options, "terminal-overrides");
a = options_array_first(o);
while (a != NULL) {
ov = options_array_item_value(a);
tty_term_override(term, ov->string);
s = ov->string;
offset = 0;
first = tty_term_override_next(s, &offset);
if (first != NULL && fnmatch(first, term->name, 0) == 0)
tty_term_apply(term, s + offset, 0);
a = options_array_next(a);
}
@ -523,19 +534,18 @@ tty_term_find(char *name, int fd, char **cause)
goto error;
}
/* Set flag if terminal has 256 colours. */
if (tty_term_number(term, TTYC_COLORS) >= 256)
term->flags |= TERM_256COLOURS;
/* Add RGB feature if terminal has RGB colours. */
if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) &&
(!tty_term_has(term, TTYC_SETRGBF) ||
!tty_term_has(term, TTYC_SETRGBB)))
tty_add_features(feat, "RGB", ":,");
/* Set flag if terminal has RGB colours. */
if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) ||
(tty_term_has(term, TTYC_SETRGBF) &&
tty_term_has(term, TTYC_SETRGBB)))
term->flags |= TERM_RGBCOLOURS;
/* Add feature if terminal has XT. */
if (tty_term_flag(term, TTYC_XT))
tty_add_features(feat, "title", ":,");
/* Set flag if terminal has synchronized updates. */
if (tty_term_flag(term, TTYC_SYNC))
term->flags |= TERM_SYNC;
/* Apply the features. */
tty_apply_features(term, *feat);
/*
* Terminals without xenl (eat newline glitch) wrap at at $COLUMNS - 1
@ -559,18 +569,6 @@ tty_term_find(char *name, int fd, char **cause)
for (; acs[0] != '\0' && acs[1] != '\0'; acs += 2)
term->acs[(u_char) acs[0]][0] = acs[1];
/* On terminals with xterm titles (XT), fill in tsl and fsl. */
if (tty_term_flag(term, TTYC_XT) &&
!tty_term_has(term, TTYC_TSL) &&
!tty_term_has(term, TTYC_FSL)) {
code = &term->codes[TTYC_TSL];
code->value.string = xstrdup("\033]0;");
code->type = TTYCODE_STRING;
code = &term->codes[TTYC_FSL];
code->value.string = xstrdup("\007");
code->type = TTYCODE_STRING;
}
/* Log the capabilities. */
for (i = 0; i < tty_term_ncodes(); i++)
log_debug("%s%s", name, tty_term_describe(term, i));
@ -587,10 +585,7 @@ tty_term_free(struct tty_term *term)
{
u_int i;
if (--term->references != 0)
return;
LIST_REMOVE(term, entry);
log_debug("removing term %s", term->name);
for (i = 0; i < tty_term_ncodes(); i++) {
if (term->codes[i].type == TTYCODE_STRING)
@ -598,6 +593,7 @@ tty_term_free(struct tty_term *term)
}
free(term->codes);
LIST_REMOVE(term, entry);
free(term->name);
free(term);
}

121
tty.c
View File

@ -74,7 +74,7 @@ static void tty_default_attributes(struct tty *, struct window_pane *,
u_int);
#define tty_use_margin(tty) \
(tty_get_flags(tty) & TERM_DECSLRM)
(tty->term->flags & TERM_DECSLRM)
#define tty_pane_full_width(tty, ctx) \
((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx)
@ -96,27 +96,19 @@ tty_create_log(void)
}
int
tty_init(struct tty *tty, struct client *c, int fd, char *term)
tty_init(struct tty *tty, struct client *c, int fd)
{
if (!isatty(fd))
return (-1);
memset(tty, 0, sizeof *tty);
if (term == NULL || *term == '\0')
tty->term_name = xstrdup("unknown");
else
tty->term_name = xstrdup(term);
tty->fd = fd;
tty->client = c;
tty->cstyle = 0;
tty->ccolour = xstrdup("");
tty->flags = 0;
tty->term_flags = 0;
return (0);
}
@ -256,7 +248,10 @@ tty_write_callback(__unused int fd, __unused short events, void *data)
int
tty_open(struct tty *tty, char **cause)
{
tty->term = tty_term_find(tty->term_name, tty->fd, cause);
struct client *c = tty->client;
tty->term = tty_term_create(tty, c->term_name, &c->term_features,
tty->fd, cause);
if (tty->term == NULL) {
tty_close(tty);
return (-1);
@ -466,28 +461,20 @@ void
tty_free(struct tty *tty)
{
tty_close(tty);
free(tty->ccolour);
free(tty->term_name);
}
void
tty_set_flags(struct tty *tty, int flags)
tty_update_features(struct tty *tty)
{
tty->term_flags |= flags;
struct client *c = tty->client;
tty_apply_features(tty->term, c->term_features);
if (tty_use_margin(tty))
tty_puts(tty, "\033[?69h"); /* DECLRMM */
}
int
tty_get_flags(struct tty *tty)
{
if (tty->term != NULL)
return (tty->term->flags|tty->term_flags);
return (tty->term_flags);
}
void
tty_raw(struct tty *tty, const char *s)
{
@ -585,7 +572,7 @@ tty_putc(struct tty *tty, u_char ch)
{
const char *acs;
if ((tty_get_flags(tty) & TERM_NOXENL) &&
if ((tty->term->flags & TERM_NOXENL) &&
ch >= 0x20 && ch != 0x7f &&
tty->cy == tty->sy - 1 &&
tty->cx + 1 >= tty->sx)
@ -611,7 +598,7 @@ tty_putc(struct tty *tty, u_char ch)
* where we think it should be after a line wrap - this
* means it works on sensible terminals as well.
*/
if (tty_get_flags(tty) & TERM_NOXENL)
if (tty->term->flags & TERM_NOXENL)
tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx);
} else
tty->cx++;
@ -621,7 +608,7 @@ tty_putc(struct tty *tty, u_char ch)
void
tty_putn(struct tty *tty, const void *buf, size_t len, u_int width)
{
if ((tty_get_flags(tty) & TERM_NOXENL) &&
if ((tty->term->flags & TERM_NOXENL) &&
tty->cy == tty->sy - 1 &&
tty->cx + len >= tty->sx)
len = tty->sx - tty->cx - 1;
@ -1179,7 +1166,7 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny,
* background colour isn't default (because it doesn't work
* after SGR 0).
*/
if ((tty_get_flags(tty) & TERM_DECFRA) && !COLOUR_DEFAULT(bg)) {
if ((tty->term->flags & TERM_DECFRA) && !COLOUR_DEFAULT(bg)) {
xsnprintf(tmp, sizeof tmp, "\033[32;%u;%u;%u;%u$x",
py + 1, px + 1, py + ny, px + nx);
tty_puts(tty, tmp);
@ -1258,7 +1245,7 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc)
return (gc);
/* UTF-8 terminal and a UTF-8 character - fine. */
if (tty_get_flags(tty) & TERM_UTF8)
if (tty->client->flags & CLIENT_UTF8)
return (gc);
/* Replace by the right number of underscores. */
@ -1440,7 +1427,7 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s,
void
tty_sync_start(struct tty *tty)
{
if ((~tty->flags & TTY_SYNCING) && (tty_get_flags(tty) & TERM_SYNC)) {
if ((~tty->flags & TTY_SYNCING) && tty_term_has(tty->term, TTYC_SYNC)) {
tty_puts(tty, "\033P=1s\033\\");
tty->flags |= TTY_SYNCING;
}
@ -1449,7 +1436,7 @@ tty_sync_start(struct tty *tty)
void
tty_sync_end(struct tty *tty)
{
if (tty_get_flags(tty) & TERM_SYNC) {
if ((tty->flags & TTY_SYNCING) && tty_term_has(tty->term, TTYC_SYNC)) {
tty_puts(tty, "\033P=2s\033\\");
tty->flags &= ~TTY_SYNCING;
}
@ -1916,7 +1903,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
ctx->xoff + ctx->ocx + ctx->num > ctx->ox + ctx->sx)) {
if (!ctx->wrapped ||
!tty_pane_full_width(tty, ctx) ||
(tty_get_flags(tty) & TERM_NOXENL) ||
(tty->term->flags & TERM_NOXENL) ||
ctx->xoff + ctx->ocx != 0 ||
ctx->yoff + ctx->ocy != tty->cy + 1 ||
tty->cx < tty->sx ||
@ -1977,7 +1964,7 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp)
const struct grid_cell *gcp;
/* Skip last character if terminal is stupid. */
if ((tty_get_flags(tty) & TERM_NOXENL) &&
if ((tty->term->flags & TERM_NOXENL) &&
tty->cy == tty->sy - 1 &&
tty->cx == tty->sx - 1)
return;
@ -2140,7 +2127,7 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx,
{
if (!ctx->wrapped ||
!tty_pane_full_width(tty, ctx) ||
(tty_get_flags(tty) & TERM_NOXENL) ||
(tty->term->flags & TERM_NOXENL) ||
ctx->xoff + cx != 0 ||
ctx->yoff + cy != tty->cy + 1 ||
tty->cx < tty->sx ||
@ -2480,14 +2467,14 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc)
/* Is this a 24-bit colour? */
if (gc->fg & COLOUR_FLAG_RGB) {
/* Not a 24-bit terminal? Translate to 256-colour palette. */
if (tty_get_flags(tty) & TERM_RGBCOLOURS)
if (tty->term->flags & TERM_RGBCOLOURS)
return;
colour_split_rgb(gc->fg, &r, &g, &b);
gc->fg = colour_find_rgb(r, g, b);
}
/* How many colours does this terminal have? */
if (tty_get_flags(tty) & TERM_256COLOURS)
if (tty->term->flags & TERM_256COLOURS)
colours = 256;
else
colours = tty_term_number(tty->term, TTYC_COLORS);
@ -2529,14 +2516,14 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc)
/* Is this a 24-bit colour? */
if (gc->bg & COLOUR_FLAG_RGB) {
/* Not a 24-bit terminal? Translate to 256-colour palette. */
if (tty_get_flags(tty) & TERM_RGBCOLOURS)
if (tty->term->flags & TERM_RGBCOLOURS)
return;
colour_split_rgb(gc->bg, &r, &g, &b);
gc->bg = colour_find_rgb(r, g, b);
}
/* How many colours does this terminal have? */
if (tty_get_flags(tty) & TERM_256COLOURS)
if (tty->term->flags & TERM_256COLOURS)
colours = 256;
else
colours = tty_term_number(tty->term, TTYC_COLORS);
@ -2597,7 +2584,7 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc)
/* Is this an aixterm bright colour? */
if (gc->fg >= 90 && gc->fg <= 97) {
if (tty_get_flags(tty) & TERM_256COLOURS) {
if (tty->term->flags & TERM_256COLOURS) {
xsnprintf(s, sizeof s, "\033[%dm", gc->fg);
tty_puts(tty, s);
} else
@ -2629,7 +2616,7 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc)
/* Is this an aixterm bright colour? */
if (gc->bg >= 90 && gc->bg <= 97) {
if (tty_get_flags(tty) & TERM_256COLOURS) {
if (tty->term->flags & TERM_256COLOURS) {
xsnprintf(s, sizeof s, "\033[%dm", gc->bg + 10);
tty_puts(tty, s);
} else
@ -2674,67 +2661,25 @@ static int
tty_try_colour(struct tty *tty, int colour, const char *type)
{
u_char r, g, b;
char s[32];
if (colour & COLOUR_FLAG_256) {
/*
* If the user has specified -2 to the client (meaning
* TERM_256COLOURS is set), setaf and setab may not work (or
* they may not want to use them), so send the usual sequence.
*
* Also if RGB is set, setaf and setab do not support the 256
* colour palette so use the sequences directly there too.
*/
if ((tty_get_flags(tty) & TERM_256COLOURS) ||
tty_term_has(tty->term, TTYC_RGB))
goto fallback_256;
/*
* If the terminfo entry has 256 colours and setaf and setab
* exist, assume that they work correctly.
*/
if (tty_get_flags(tty) & TERM_256COLOURS) {
if (*type == '3') {
if (!tty_term_has(tty->term, TTYC_SETAF))
goto fallback_256;
tty_putcode1(tty, TTYC_SETAF, colour & 0xff);
} else {
if (!tty_term_has(tty->term, TTYC_SETAB))
goto fallback_256;
tty_putcode1(tty, TTYC_SETAB, colour & 0xff);
}
return (0);
}
goto fallback_256;
if (*type == '3' && tty_term_has(tty->term, TTYC_SETAF))
tty_putcode1(tty, TTYC_SETAF, colour & 0xff);
else if (tty_term_has(tty->term, TTYC_SETAB))
tty_putcode1(tty, TTYC_SETAB, colour & 0xff);
return (0);
}
if (colour & COLOUR_FLAG_RGB) {
colour_split_rgb(colour & 0xffffff, &r, &g, &b);
if (*type == '3') {
if (!tty_term_has(tty->term, TTYC_SETRGBF))
goto fallback_rgb;
if (*type == '3' && tty_term_has(tty->term, TTYC_SETRGBF))
tty_putcode3(tty, TTYC_SETRGBF, r, g, b);
} else {
if (!tty_term_has(tty->term, TTYC_SETRGBB))
goto fallback_rgb;
else if (tty_term_has(tty->term, TTYC_SETRGBB))
tty_putcode3(tty, TTYC_SETRGBB, r, g, b);
}
return (0);
}
return (-1);
fallback_256:
xsnprintf(s, sizeof s, "\033[%s;5;%dm", type, colour & 0xff);
log_debug("%s: 256 colour fallback: %s", tty->client->name, s);
tty_puts(tty, s);
return (0);
fallback_rgb:
xsnprintf(s, sizeof s, "\033[%s;2;%d;%d;%dm", type, r, g, b);
log_debug("%s: RGB colour fallback: %s", tty->client->name, s);
tty_puts(tty, s);
return (0);
}
static void