1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Restore ability to not send SSH terminal modes.

2ce0b680c inadvertently removed this ability in trying to ensure that
everyone got the new IUTF8 mode by default; you could remove a mode from
the list in the UI, but this would just revert PuTTY to its default.

The UI and storage have been revamped; the storage format now explicitly
says when a mode is not to be sent, and the configuration UI always
shows all modes known to PuTTY; if a mode is not to be sent it now shows
up as "(don't send)" in the list.

Old saved settings are migrated so as to preserve previous removals of
longstanding modes, while automatically adding IUTF8.

(In passing, this removes a bug where pressing the 'Remove' button of
the previous UI would populate the value edit box with garbage.)
This commit is contained in:
Jacob Nevins 2017-03-06 10:36:26 +00:00
parent 2ef799da4d
commit 2d0b2e97d0
4 changed files with 137 additions and 108 deletions

133
config.c
View File

@ -946,8 +946,7 @@ static void colour_handler(union control *ctrl, void *dlg,
}
struct ttymodes_data {
union control *modelist, *valradio, *valbox;
union control *addbutton, *rembutton, *listbox;
union control *valradio, *valbox, *setbutton, *listbox;
};
static void ttymodes_handler(union control *ctrl, void *dlg,
@ -966,69 +965,67 @@ static void ttymodes_handler(union control *ctrl, void *dlg,
val != NULL;
val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) {
char *disp = dupprintf("%s\t%s", key,
(val[0] == 'A') ? "(auto)" : val+1);
(val[0] == 'A') ? "(auto)" :
((val[0] == 'N') ? "(don't send)"
: val+1));
dlg_listbox_add(ctrl, dlg, disp);
sfree(disp);
}
dlg_update_done(ctrl, dlg);
} else if (ctrl == td->modelist) {
int i;
dlg_update_start(ctrl, dlg);
dlg_listbox_clear(ctrl, dlg);
for (i = 0; ttymodes[i]; i++)
dlg_listbox_add(ctrl, dlg, ttymodes[i]);
dlg_listbox_select(ctrl, dlg, 0); /* *shrug* */
dlg_update_done(ctrl, dlg);
} else if (ctrl == td->valradio) {
dlg_radiobutton_set(ctrl, dlg, 0);
}
} else if (event == EVENT_SELCHANGE) {
if (ctrl == td->listbox) {
int ind = dlg_listbox_index(td->listbox, dlg);
char *val;
if (ind < 0) {
return; /* no item selected */
}
val = conf_get_str_str(conf, CONF_ttymodes,
conf_get_str_nthstrkey(conf, CONF_ttymodes,
ind));
assert(val != NULL);
/* Do this first to defuse side-effects on radio buttons: */
dlg_editbox_set(td->valbox, dlg, val+1);
dlg_radiobutton_set(td->valradio, dlg,
val[0] == 'A' ? 0 : (val[0] == 'N' ? 1 : 2));
}
} else if (event == EVENT_VALCHANGE) {
if (ctrl == td->valbox) {
/* If they're editing the text box, we assume they want its
* value to be used. */
dlg_radiobutton_set(td->valradio, dlg, 2);
}
} else if (event == EVENT_ACTION) {
if (ctrl == td->addbutton) {
int ind = dlg_listbox_index(td->modelist, dlg);
if (ctrl == td->setbutton) {
int ind = dlg_listbox_index(td->listbox, dlg);
const char *key;
char *str, *val;
char type;
{
const char *types = "ANV";
int button = dlg_radiobutton_get(td->valradio, dlg);
assert(button >= 0 && button < lenof(types));
type = types[button];
}
/* Construct new entry */
if (ind >= 0) {
char type = dlg_radiobutton_get(td->valradio, dlg) ? 'V' : 'A';
const char *key;
char *str, *val;
/* Construct new entry */
key = ttymodes[ind];
str = dlg_editbox_get(td->valbox, dlg);
key = conf_get_str_nthstrkey(conf, CONF_ttymodes, ind);
str = (type == 'V' ? dlg_editbox_get(td->valbox, dlg)
: dupstr(""));
val = dupprintf("%c%s", type, str);
sfree(str);
conf_set_str_str(conf, CONF_ttymodes, key, val);
sfree(val);
dlg_refresh(td->listbox, dlg);
} else
dlg_listbox_select(td->listbox, dlg, ind);
} else {
/* Not a multisel listbox, so this means nothing selected */
dlg_beep(dlg);
} else if (ctrl == td->rembutton) {
int i = 0;
char *key, *val;
int multisel = dlg_listbox_index(td->listbox, dlg) < 0;
for (val = conf_get_str_strs(conf, CONF_ttymodes, NULL, &key);
val != NULL;
val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) {
if (dlg_listbox_issel(td->listbox, dlg, i)) {
if (!multisel) {
/* Populate controls with entry we're about to
* delete, for ease of editing.
* (If multiple entries were selected, don't
* touch the controls.) */
int ind = 0;
val++;
while (ttymodes[ind]) {
if (!strcmp(ttymodes[ind], key))
break;
ind++;
}
dlg_listbox_select(td->modelist, dlg, ind);
dlg_radiobutton_set(td->valradio, dlg,
(*val == 'V'));
dlg_editbox_set(td->valbox, dlg, val+1);
}
conf_del_str_str(conf, CONF_ttymodes, key);
}
i++;
}
dlg_refresh(td->listbox, dlg);
}
}
}
@ -2491,54 +2488,40 @@ void setup_config_box(struct controlbox *b, int midsession,
"Terminal modes");
td = (struct ttymodes_data *)
ctrl_alloc(b, sizeof(struct ttymodes_data));
ctrl_columns(s, 2, 75, 25);
c = ctrl_text(s, "Terminal modes to send:", HELPCTX(ssh_ttymodes));
c->generic.column = 0;
td->rembutton = ctrl_pushbutton(s, "Remove", 'r',
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td));
td->rembutton->generic.column = 1;
td->rembutton->generic.tabdelay = 1;
ctrl_columns(s, 1, 100);
td->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td));
td->listbox->listbox.multisel = 1;
td->listbox->listbox.height = 4;
td->listbox->listbox.height = 8;
td->listbox->listbox.ncols = 2;
td->listbox->listbox.percentages = snewn(2, int);
td->listbox->listbox.percentages[0] = 40;
td->listbox->listbox.percentages[1] = 60;
ctrl_tabdelay(s, td->rembutton);
ctrl_columns(s, 2, 75, 25);
td->modelist = ctrl_droplist(s, "Mode:", 'm', 67,
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td));
td->modelist->generic.column = 0;
td->addbutton = ctrl_pushbutton(s, "Add", 'd',
c = ctrl_text(s, "For selected mode, send:", HELPCTX(ssh_ttymodes));
c->generic.column = 0;
td->setbutton = ctrl_pushbutton(s, "Set", 's',
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td));
td->addbutton->generic.column = 1;
td->addbutton->generic.tabdelay = 1;
td->setbutton->generic.column = 1;
td->setbutton->generic.tabdelay = 1;
ctrl_columns(s, 1, 100); /* column break */
/* Bit of a hack to get the value radio buttons and
* edit-box on the same row. */
ctrl_columns(s, 3, 25, 50, 25);
c = ctrl_text(s, "Value:", HELPCTX(ssh_ttymodes));
c->generic.column = 0;
td->valradio = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 2,
ctrl_columns(s, 2, 75, 25);
td->valradio = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3,
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td),
"Auto", NO_SHORTCUT, P(NULL),
"Nothing", NO_SHORTCUT, P(NULL),
"This:", NO_SHORTCUT, P(NULL),
NULL);
td->valradio->generic.column = 1;
td->valradio->generic.column = 0;
td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100,
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td), P(NULL));
td->valbox->generic.column = 2;
ctrl_tabdelay(s, td->addbutton);
td->valbox->generic.column = 1;
ctrl_tabdelay(s, td->setbutton);
}
if (!midsession) {

View File

@ -2934,24 +2934,17 @@ the remote pseudo-terminal. These usually control the server's
expectation of the local terminal's behaviour.
If your server does not have sensible defaults for these modes, you
may find that changing them here helps. If you don't understand any of
this, it's safe to leave these settings alone.
may find that changing them here helps, although the server is at
liberty to ignore your changes. If you don't understand any of this,
it's safe to leave these settings alone.
(None of these settings will have any effect if no pseudo-terminal
is requested or allocated.)
You can add or modify a mode by selecting it from the drop-down list,
choosing whether it's set automatically or to a specific value with
the radio buttons and edit box, and hitting \q{Add}. A mode (or
several) can be removed from the list by selecting them and hitting
\q{Remove}. The effect of the mode list is as follows:
\b If a mode is not on the list, it will not be specified to the
server under any circumstances.
\b If a mode is on the list:
\lcont{
You can change what happens for a particular mode by selecting it in
the list, choosing one of the options and specifying the exact value
if necessary, and hitting \q{Set}. The effect of the options is as
follows:
\b If the \q{Auto} option is selected, the PuTTY tools will decide
whether to specify that mode to the server, and if so, will send
@ -2966,12 +2959,13 @@ modes from the local terminal, if any.
}
\b If \q{Nothing} is selected, no value for the mode will not be
specified to the server under any circumstances.
\b If a value is specified, it will be sent to the server under all
circumstances. The precise syntax of the value box depends on the
mode.
}
By default, all of the available modes are listed as \q{Auto},
which should do the right thing in most circumstances.
@ -3008,18 +3002,22 @@ character or turn it off entirely.
\b Boolean modes such as \cw{ECHO} and \cw{ICANON} can be specified in
PuTTY in a variety of ways, such as \cw{true}/\cw{false},
\cw{yes}/\cw{no}, and \cw{0}/\cw{1}.
\cw{yes}/\cw{no}, and \cw{0}/\cw{1}. (Explicitly specifying a value of
\cw{no} is different from not sending the mode at all.)
\b The boolean mode \I{IUTF8 terminal mode}\cw{IUTF8} signals to the
server whether the terminal character set is \i{UTF-8} or not.
If this is set incorrectly, actions like backspace may behave
incorrectly in some circumstances. However, setting this is not usually
If this is set incorrectly, keys like backspace may do the wrong thing
in some circumstances. However, setting this is not usually
sufficient to cause servers to expect the terminal to be in UTF-8 mode;
POSIX servers will generally require the locale to be set (by some
server-dependent means), although many default to UTF-8. Also,
\#{circa 2016} many servers (particularly older servers) do not honour
this mode sent over SSH. When set to \q{Auto}, this follows the
local configured character set (see \k{config-charset}).
since this mode was added to the SSH protocol much later than the
others, \#{circa 2016} many servers (particularly older servers) do
not honour this mode sent over SSH; indeed, a few poorly-written
servers object to its mere presence, so you may find you need to set
it to not be sent at all. When set to \q{Auto}, this follows the local
configured character set (see \k{config-charset}).
\b Terminal speeds are configured elsewhere; see \k{config-termspeed}.

View File

@ -46,6 +46,9 @@ static const struct keyvalwhere hknames[] = {
* This is currently precisely the same as the set in ssh.c, but could
* in principle differ if other backends started to support tty modes
* (e.g., the pty backend).
* The set of modes in in this array is currently significant for
* settings migration from old versions; if they change, review the
* gppmap() invocation for "TerminalModes".
*/
const char *const ttymodes[] = {
"INTR", "QUIT", "ERASE", "KILL", "EOF",
@ -748,7 +751,51 @@ void load_open_settings(void *sesskey, Conf *conf)
gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives);
gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype);
gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed);
if (!gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) {
if (gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) {
/*
* Backwards compatibility with old saved settings.
*
* From the invention of this setting through 0.67, the set of
* terminal modes was fixed, and absence of a mode from this
* setting meant the user had explicitly removed it from the
* UI and we shouldn't send it.
*
* In 0.68, the IUTF8 mode was added, and in handling old
* settings we inadvertently removed the ability to not send
* a mode. Any mode not mentioned was treated as if it was
* set to 'auto' (A).
*
* After 0.68, we added explicit notation to the setting format
* when the user removes a known terminal mode from the list.
*
* So: if any of the modes from the original set is missing, we
* assume this was an intentional removal by the user and add
* an explicit removal ('N'); but if IUTF8 (or any other mode
* added after 0.67) is missing, we assume that its absence is
* due to the setting being old rather than intentional, and
* add it with its default setting.
*
* (This does mean that if a 0.68 user explicitly removed IUTF8,
* we add it back; but removing IUTF8 had no effect in 0.68, so
* we're preserving behaviour, which is the best we can do.)
*/
for (i = 0; ttymodes[i]; i++) {
if (!conf_get_str_str_opt(conf, CONF_ttymodes, ttymodes[i])) {
/* Mode not mentioned in setting. */
const char *def;
if (!strcmp(ttymodes[i], "IUTF8")) {
/* Any new modes we add in future should be treated
* this way too. */
def = "A"; /* same as new-setting default below */
} else {
/* One of the original modes. Absence is probably
* deliberate. */
def = "N"; /* don't send */
}
conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], def);
}
}
} else {
/* This hardcodes a big set of defaults in any new saved
* sessions. Let's hope we don't change our mind. */
for (i = 0; ttymodes[i]; i++)

21
ssh.c
View File

@ -1036,20 +1036,20 @@ static void parse_ttymodes(Ssh ssh,
int i;
const struct ssh_ttymode *mode;
char *val;
char default_val[2];
strcpy(default_val, "A");
for (i = 0; i < lenof(ssh_ttymodes); i++) {
mode = ssh_ttymodes + i;
val = conf_get_str_str_opt(ssh->conf, CONF_ttymodes, mode->mode);
if (!val)
val = default_val;
/* Every mode known to the current version of the code should be
* mentioned; this was ensured when settings were loaded. */
val = conf_get_str_str(ssh->conf, CONF_ttymodes, mode->mode);
/*
* val[0] is either 'V', indicating that an explicit value
* follows it, or 'A' indicating that we should pass the
* value through from the local environment via get_ttymode.
* val[0] can be
* - 'V', indicating that an explicit value follows it;
* - 'A', indicating that we should pass the value through from
* the local environment via get_ttymode; or
* - 'N', indicating that we should explicitly not send this
* mode.
*/
if (val[0] == 'A') {
val = get_ttymode(ssh->frontend, mode->mode);
@ -1057,8 +1057,9 @@ static void parse_ttymodes(Ssh ssh,
do_mode(data, mode, val);
sfree(val);
}
} else
} else if (val[0] == 'V') {
do_mode(data, mode, val + 1); /* skip the 'V' */
} /* else 'N', or something from the future we don't understand */
}
}