fix setting up and using the terminfo data

There should be just one place that calls `setupterm()`. While refactoring
the code I also decided to not make initializing the curses subsystem a
fatal error. We now try two fallback terminal names ("ansi" and "dumb")
and if those can't be used we still end up with a usable shell.

Fixes #3850
This commit is contained in:
Kurtis Rader 2017-02-15 20:09:26 -08:00
parent 42a320064c
commit 6d02bec4c7
16 changed files with 282 additions and 237 deletions

View file

@ -972,7 +972,7 @@ obj/builtin_set.o: config.h src/builtin.h src/common.h src/fallback.h
obj/builtin_set.o: src/signal.h src/env.h src/expand.h src/parse_constants.h
obj/builtin_set.o: src/io.h src/proc.h src/parse_tree.h src/tokenizer.h
obj/builtin_set.o: src/wgetopt.h src/wutil.h
obj/builtin_set_color.o: config.h src/builtin.h src/common.h src/fallback.h
obj/builtin_set_color.o: config.h src/builtin.h src/common.h src/env.h src/fallback.h
obj/builtin_set_color.o: src/signal.h src/color.h src/io.h src/output.h
obj/builtin_set_color.o: src/proc.h src/parse_tree.h src/parse_constants.h
obj/builtin_set_color.o: src/tokenizer.h src/wgetopt.h src/wutil.h

View file

@ -93,7 +93,9 @@
{ symbol: ["uint32_t", "private", "<stdint.h>", "public"] },
{ symbol: ["intptr_t", "private", "<stdint.h>", "public"] },
{ symbol: ["tparm", "private", "<ncurses.h>", "public"] },
{ symbol: ["tigetflag", "private", "<ncurses.h>", "public"] },
{ symbol: ["ERR", "private", "<ncurses.h>", "public"] },
{ symbol: ["OK", "private", "<ncurses.h>", "public"] },
{ symbol: ["select", "private", "<sys/select.h>", "public"] },
{ symbol: ["_LIBCPP_VERSION", "private", "<stddef.h>", "public"] },
{ symbol: ["_LIBCPP_VERSION", "private", "<unistd.h>", "public"] },

View file

@ -3,7 +3,6 @@
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#if HAVE_NCURSES_H
#include <ncurses.h>
@ -25,6 +24,7 @@
#include "builtin.h"
#include "color.h"
#include "common.h"
#include "env.h"
#include "io.h"
#include "output.h"
#include "proc.h"
@ -52,6 +52,9 @@ static int set_color_builtin_outputter(char c) {
/// set_color builtin.
int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
// By the time this is called we should have initialized the curses subsystem.
assert(curses_initialized);
wgetopter_t w;
// Variables used for parsing the argument list.
const struct woption long_options[] = {{L"background", required_argument, 0, 'b'},
@ -66,7 +69,6 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
{0, 0, 0, 0}};
const wchar_t *short_options = L"b:hvoidrcu";
int argc = builtin_count_args(argv);
// Some code passes variables to set_color that don't exist, like $fish_user_whatever. As a
@ -77,7 +79,6 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
const wchar_t *bgcolor = NULL;
bool bold = false, underline = false, italics = false, dim = false, reverse = false;
int errret;
// Parse options to obtain the requested operation and the modifiers.
w.woptind = 0;
@ -162,12 +163,6 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
return STATUS_BUILTIN_ERROR;
}
// Make sure that the term exists.
if (cur_term == NULL && setupterm(0, STDOUT_FILENO, &errret) == ERR) {
streams.err.append_format(_(L"%ls: Could not set up terminal\n"), argv[0]);
return STATUS_BUILTIN_ERROR;
}
// Test if we have at least basic support for setting fonts, colors and related bits - otherwise
// just give up...
if (cur_term == NULL || !exit_attribute_mode) {

View file

@ -6,6 +6,7 @@
#include <pthread.h>
#include <pwd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
@ -14,6 +15,13 @@
#include <unistd.h>
#include <wchar.h>
#if HAVE_NCURSES_H
#include <ncurses.h>
#elif HAVE_NCURSES_CURSES_H
#include <ncurses/curses.h>
#else
#include <curses.h>
#endif
#if HAVE_TERM_H
#include <term.h>
#elif HAVE_NCURSES_TERM_H
@ -37,6 +45,7 @@
#include "history.h"
#include "input.h"
#include "input_common.h"
#include "output.h"
#include "path.h"
#include "proc.h"
#include "reader.h"
@ -46,6 +55,8 @@
/// Value denoting a null string.
#define ENV_NULL L"\x1d"
#define DEFAULT_TERM1 "ansi"
#define DEFAULT_TERM2 "dumb"
/// Some configuration path environment variables.
#define FISH_DATADIR_VAR L"__fish_datadir"
@ -62,20 +73,34 @@ extern char **environ;
size_t read_byte_limit = READ_BYTE_LIMIT;
bool g_use_posix_spawn = false; // will usually be set to true
bool curses_initialized = false;
/// Does the terminal have the "eat_newline_glitch".
bool term_has_xn = false;
/// List of all locale environment variable names.
/// This is used to ensure that we don't perform any callbacks from `react_to_variable_change()`
/// when we're importing environment variables in `env_init()`. That's because we don't have any
/// control over the order in which the vars are imported and some of them work in combination.
/// For example, `TERMINFO_DIRS` and `TERM`. If the user has set `TERM` to a custom value that is
/// found in `TERMINFO_DIRS` we don't to call `handle_curses()` before we've imported the latter.
static bool env_initialized = false;
/// List of all locale environment variable names that might trigger (re)initializing the locale
/// subsystem.
static const wchar_t *const locale_variable[] = {
L"LANG", L"LANGUAGE", L"LC_ALL", L"LC_ADDRESS", L"LC_COLLATE",
L"LC_CTYPE", L"LC_IDENTIFICATION", L"LC_MEASUREMENT", L"LC_MESSAGES", L"LC_MONETARY",
L"LC_NAME", L"LC_NUMERIC", L"LC_PAPER", L"LC_TELEPHONE", L"LC_TIME",
NULL};
/// List of all curses environment variable names.
/// List of all curses environment variable names that might trigger (re)initializing the curses
/// subsystem.
static const wchar_t *const curses_variable[] = {L"TERM", L"TERMINFO", L"TERMINFO_DIRS", NULL};
// Some forward declarations to make it easy to logically group the code.
static void init_locale();
static void init_curses();
// Struct representing one level in the function variable stack.
// Only our variable stack should create and destroy these
class env_node_t {
@ -105,7 +130,6 @@ class variable_entry_t {
static pthread_mutex_t env_lock = PTHREAD_MUTEX_INITIALIZER;
static bool local_scope_exports(const env_node_t *n);
static void handle_locale(const wchar_t *env_var_name);
// A class wrapping up a variable stack
// Currently there is only one variable stack in fish,
@ -136,6 +160,8 @@ struct var_stack_t {
// Pops the top node if it's not global
void pop();
bool var_changed(wchar_t const *const vars[]);
// Returns the next scope to search for a given node, respecting the new_scope lag
// Returns NULL if we're done
env_node_t *next_scope_to_search(env_node_t *node);
@ -156,23 +182,24 @@ void var_stack_t::push(bool new_scope) {
}
}
/// Return true if one of the vars in the passed list was changed in the current var scope.
bool var_stack_t::var_changed(wchar_t const *const vars[]) {
for (auto v = vars; *v; v++) {
if (top->env.find(*v) != top->env.end()) return true;
}
return false;
}
void var_stack_t::pop() {
// Don't pop the global
if (this->top.get() == this->global_env) {
// Don't pop the top-most, global, level.
if (top.get() == this->global_env) {
debug(0, _(L"Tried to pop empty environment stack."));
sanity_lose();
return;
}
const wchar_t *locale_changed = NULL;
for (int i = 0; locale_variable[i]; i++) {
var_table_t::iterator result = top->env.find(locale_variable[i]);
if (result != top->env.end()) {
locale_changed = locale_variable[i];
break;
}
}
bool locale_changed = this->var_changed(locale_variable);
bool curses_changed = this->var_changed(curses_variable);
if (top->new_scope) { //!OCLINT(collapsible if statements)
if (top->exportv || local_scope_exports(top->next.get())) {
@ -196,8 +223,9 @@ void var_stack_t::pop() {
break;
}
}
// TODO: Move this to something general.
if (locale_changed) handle_locale(locale_changed);
if (locale_changed) init_locale();
if (curses_changed) init_curses();
}
const env_node_t *var_stack_t::next_scope_to_search(const env_node_t *node) const {
@ -303,25 +331,27 @@ static bool var_is_locale(const wcstring &key) {
return false;
}
/// Properly sets all locale information.
static void handle_locale(const wchar_t *env_var_name) {
debug(2, L"handle_locale() called in response to '%ls' changing", env_var_name);
/// Initialize the locale subsystem.
static void init_locale() {
// We have to make a copy because the subsequent setlocale() call to change the locale will
// invalidate the pointer from the this setlocale() call.
char *old_msg_locale = strdup(setlocale(LC_MESSAGES, NULL));
const env_var_t val = env_get_string(env_var_name, ENV_EXPORT);
for (const wchar_t *const *var_name = locale_variable; *var_name; var_name++) {
const env_var_t val = env_get_string(*var_name, ENV_EXPORT);
const std::string &name = wcs2string(*var_name);
const std::string &value = wcs2string(val);
const std::string &name = wcs2string(env_var_name);
debug(2, L"locale var %s='%s'", name.c_str(), value.c_str());
if (val.empty()) {
unsetenv(name.c_str());
} else {
setenv(name.c_str(), value.c_str(), 1);
}
}
char *locale = setlocale(LC_ALL, "");
fish_setlocale();
debug(2, L"handle_locale() setlocale(): '%s'", locale);
debug(2, L"init_locale() setlocale(): '%s'", locale);
const char *new_msg_locale = setlocale(LC_MESSAGES, NULL);
debug(3, L"old LC_MESSAGES locale: '%s'", old_msg_locale);
@ -380,15 +410,84 @@ static bool does_term_support_setting_title() {
return true;
}
/// Handle changes to the TERM env var that do not involves the curses subsystem.
static void handle_term() { can_set_term_title = does_term_support_setting_title(); }
/// Updates our idea of whether we support term256 and term24bit (see issue #10222).
static void update_fish_color_support() {
// Detect or infer term256 support. If fish_term256 is set, we respect it;
// otherwise infer it from the TERM variable or use terminfo.
env_var_t fish_term256 = env_get_string(L"fish_term256");
env_var_t term = env_get_string(L"TERM");
bool support_term256 = false; // default to no support
if (!fish_term256.missing_or_empty()) {
support_term256 = from_string<bool>(fish_term256);
debug(2, L"256 color support determined by 'fish_term256'");
} else if (term.find(L"256color") != wcstring::npos) {
// TERM=*256color*: Explicitly supported.
support_term256 = true;
debug(2, L"256 color support enabled for '256color' in TERM");
} else if (term.find(L"xterm") != wcstring::npos) {
// Assume that all xterms are 256, except for OS X SnowLeopard
const env_var_t prog = env_get_string(L"TERM_PROGRAM");
const env_var_t progver = env_get_string(L"TERM_PROGRAM_VERSION");
if (prog == L"Apple_Terminal" && !progver.missing_or_empty()) {
// OS X Lion is version 300+, it has 256 color support
if (strtod(wcs2str(progver), NULL) > 300) {
support_term256 = true;
debug(2, L"256 color support enabled for TERM=xterm + modern Terminal.app");
}
} else {
support_term256 = true;
debug(2, L"256 color support enabled for TERM=xterm");
}
} else if (cur_term != NULL) {
// See if terminfo happens to identify 256 colors
support_term256 = (max_colors >= 256);
debug(2, L"256 color support: using %d colors per terminfo", max_colors);
} else {
debug(2, L"256 color support not enabled (yet)");
}
/// Push all curses/terminfo env vars into the global environment where they can be found by those
/// libraries.
static void handle_curses(const wchar_t *env_var_name) {
debug(2, L"handle_curses() called in response to '%ls' changing", env_var_name);
const env_var_t val = env_get_string(env_var_name, ENV_EXPORT);
const std::string &name = wcs2string(env_var_name);
env_var_t fish_term24bit = env_get_string(L"fish_term24bit");
bool support_term24bit;
if (!fish_term24bit.missing_or_empty()) {
support_term24bit = from_string<bool>(fish_term24bit);
debug(2, L"'fish_term24bit' preference: 24-bit color %s",
support_term24bit ? L"enabled" : L"disabled");
} else {
// We don't attempt to infer term24 bit support yet.
support_term24bit = false;
}
color_support_t support = (support_term256 ? color_support_term256 : 0) |
(support_term24bit ? color_support_term24bit : 0);
output_set_color_support(support);
}
// Try to initialize the terminfo/curses subsystem using our fallback terminal name. Do not set
// `TERM` to our fallback. We're only doing this in the hope of getting a minimally functional
// shell. If we launch an external command that uses TERM it should get the same value we were
// given, if any.
static bool initialize_curses_using_fallback(const char *term) {
// If $TERM is already set to the fallback name we're about to use there isn't any point in
// seeing if the fallback name can be used.
const char *term_env = wcs2str(env_get_string(L"TERM"));
if (!strcmp(term_env, DEFAULT_TERM1) || !strcmp(term_env, DEFAULT_TERM2)) return false;
if (is_interactive_session) {
debug(1, _(L"Using fallback terminal type '%s'."), term);
}
int err_ret;
if (setupterm((char *)term, STDOUT_FILENO, &err_ret) == OK) return true;
if (is_interactive_session) {
debug(1, _(L"Could not set up terminal using the fallback terminal type '%s'."), term);
}
return false;
}
/// Initialize the curses subsystem.
static void init_curses() {
for (const wchar_t *const *var_name = curses_variable; *var_name; var_name++) {
const env_var_t val = env_get_string(*var_name, ENV_EXPORT);
const std::string &name = wcs2string(*var_name);
const std::string &value = wcs2string(val);
debug(2, L"curses var %s='%s'", name.c_str(), value.c_str());
if (val.empty()) {
@ -396,23 +495,45 @@ static void handle_curses(const wchar_t *env_var_name) {
} else {
setenv(name.c_str(), value.c_str(), 1);
}
// TODO: Modify input_init() to allow calling it when the terminfo env vars are dynamically
// changed. At the present time it can be called just once. Also, we should really only do this
// if the TERM var is set.
// input_init();
term_has_xn = tgetflag((char *)"xn") == 1; // does terminal have the eat_newline_glitch
}
int err_ret;
if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) {
env_var_t term = env_get_string(L"TERM");
if (is_interactive_session) {
debug(1, _(L"Could not set up terminal."));
if (term.missing_or_empty()) {
debug(1, _(L"TERM environment variable not set."));
} else {
debug(1, _(L"TERM environment variable set to '%ls'."), term.c_str());
debug(1, _(L"Check that this terminal type is supported on this system."));
}
}
if (!initialize_curses_using_fallback(DEFAULT_TERM1)) {
initialize_curses_using_fallback(DEFAULT_TERM2);
}
}
can_set_term_title = does_term_support_setting_title();
term_has_xn = tigetflag((char *)"xenl") == 1; // does terminal have the eat_newline_glitch
update_fish_color_support();
// Invalidate the cached escape sequences since they may no longer be valid.
cached_esc_sequences.clear();
curses_initialized = true;
}
/// React to modifying the given variable.
static void react_to_variable_change(const wcstring &key) {
// Don't do any of this until `env_init()` has run. We only want to do this in response to
// variables set by the user; e.g., in a script like *config.fish* or interactively.
if (!env_initialized) return;
if (var_is_locale(key)) {
handle_locale(key.c_str());
init_locale();
} else if (var_is_curses(key)) {
handle_curses(key.c_str());
if (key == L"TERM") handle_term();
init_curses();
init_input();
} else if (var_is_timezone(key)) {
handle_timezone(key.c_str());
} else if (key == L"fish_term256" || key == L"fish_term24bit") {
@ -468,7 +589,7 @@ static void setup_path() {
}
}
/// Initialize the `COLUMNS` and `LINES` env vars if they don't already exist to reasonable
/// If they don't already exist initialize the `COLUMNS` and `LINES` env vars to reasonable
/// defaults. They will be updated later by the `get_current_winsize()` function if they need to be
/// adjusted.
static void env_set_termsize() {
@ -533,6 +654,42 @@ static void setup_user(bool force) {
}
}
/// Various things we need to initialize at run-time that don't really fit any of the other init
/// routines.
void misc_init() {
env_set_read_limit();
// If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might
// be intermixed with `write()` calls and we need to ensure the writes are not reordered. See
// issue #3748.
if (isatty(STDOUT_FILENO)) {
fflush(stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}
#ifdef OS_IS_CYGWIN
// MS Windows tty devices do not currently have either a read or write timestamp. Those
// respective fields of `struct stat` are always the current time. Which means we can't
// use them. So we assume no external program has written to the terminal behind our
// back. This makes multiline promptusable. See issue #2859 and
// https://github.com/Microsoft/BashOnWindows/issues/545
has_working_tty_timestamps = false;
#else
// This covers preview builds of Windows Subsystem for Linux (WSL).
FILE *procsyskosrel;
if ((procsyskosrel = wfopen(L"/proc/sys/kernel/osrelease", "r"))) {
wcstring osrelease;
fgetws2(&osrelease, procsyskosrel);
if (osrelease.find(L"3.4.0-Microsoft") != wcstring::npos) {
has_working_tty_timestamps = false;
}
}
if (procsyskosrel) {
fclose(procsyskosrel);
}
#endif // OS_IS_MS_WINDOWS
}
void env_init(const struct config_paths_t *paths /* or NULL */) {
// These variables can not be altered directly by the user.
const wchar_t *const ro_keys[] = {
@ -585,6 +742,10 @@ void env_init(const struct config_paths_t *paths /* or NULL */) {
env_set(FISH_BIN_DIR, paths->bin.c_str(), ENV_GLOBAL);
}
init_locale();
init_curses();
init_input();
// Set up the USER and PATH variables
setup_path();
setup_user(false);
@ -647,6 +808,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) {
// scope will persist throughout the lifetime of the fish process, and it will ensure that `set
// -l` commands run at the command-line don't affect the global scope.
env_push(false);
env_initialized = true;
}
/// Search all visible scopes in order for the specified key. Return the first scope in which it was

View file

@ -12,6 +12,7 @@
#include "common.h"
extern size_t read_byte_limit;
extern bool curses_initialized;
// Flags that may be passed as the 'mode' in env_set / env_get_string.
enum {
@ -54,6 +55,10 @@ struct config_paths_t {
/// Initialize environment variable data.
void env_init(const struct config_paths_t *paths = NULL);
/// Various things we need to initialize at run-time that don't really fit any of the other init
/// routines.
void misc_init();
int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t mode);
class env_var_t : public wcstring {

View file

@ -44,7 +44,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#include "fish_version.h"
#include "function.h"
#include "history.h"
#include "input.h"
#include "io.h"
#include "parser.h"
#include "path.h"
@ -318,42 +317,6 @@ static int fish_parse_opt(int argc, char **argv, std::vector<std::string> *cmds)
return optind;
}
/// Various things we need to initialize at run-time that don't really fit any of the other init
/// routines.
static void misc_init() {
env_set_read_limit();
// If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might
// be intermixed with `write()` calls and we need to ensure the writes are not reordered. See
// issue #3748.
if (isatty(STDOUT_FILENO)) {
fflush(stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}
#ifdef OS_IS_CYGWIN
// MS Windows tty devices do not currently have either a read or write timestamp. Those
// respective fields of `struct stat` are always the current time. Which means we can't
// use them. So we assume no external program has written to the terminal behind our
// back. This makes multiline promptusable. See issue #2859 and
// https://github.com/Microsoft/BashOnWindows/issues/545
has_working_tty_timestamps = false;
#else
// This covers preview builds of Windows Subsystem for Linux (WSL).
FILE *procsyskosrel;
if ((procsyskosrel = wfopen(L"/proc/sys/kernel/osrelease", "r"))) {
wcstring osrelease;
fgetws2(&osrelease, procsyskosrel);
if (osrelease.find(L"3.4.0-Microsoft") != wcstring::npos) {
has_working_tty_timestamps = false;
}
}
if (procsyskosrel) {
fclose(procsyskosrel);
}
#endif // OS_IS_MS_WINDOWS
}
int main(int argc, char **argv) {
int res = 1;
int my_optind = 0;
@ -389,17 +352,14 @@ int main(int argc, char **argv) {
}
const struct config_paths_t paths = determine_config_directory_paths(argv[0]);
env_init(&paths);
proc_init();
event_init();
builtin_init();
function_init();
env_init(&paths);
misc_init();
reader_init();
history_init();
// For set_color to support term256 in config.fish (issue #1022).
update_fish_color_support();
misc_init();
parser_t &parser = parser_t::principal_parser();

View file

@ -36,7 +36,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#include "env.h"
#include "fish_version.h"
#include "highlight.h"
#include "input.h"
#include "output.h"
#include "parse_constants.h"
#include "parse_tree.h"
@ -347,7 +346,6 @@ int main(int argc, char *argv[]) {
// (e.g., "# -*- coding: <encoding-name> -*-").
setlocale(LC_ALL, "");
env_init();
input_init();
// Types of output we support.
enum {

View file

@ -294,7 +294,6 @@ static void setup_and_process_keys(bool continuous_mode) {
setup_fork_guards();
env_init();
reader_init();
input_init();
proc_push_interactive(1);
install_our_signal_handlers();

View file

@ -921,7 +921,7 @@ static void test_utf82wchar(const char *src, size_t slen, const wchar_t *dst, si
size = utf8_to_wchar(src, slen, NULL, flags);
} else {
mem = (wchar_t *)malloc(dlen * sizeof(*mem));
if (mem == NULL) {
if (!mem) {
err(L"u2w: %s: MALLOC FAILED\n", descr);
return;
}
@ -969,7 +969,7 @@ static void test_wchar2utf8(const wchar_t *src, size_t slen, const char *dst, si
if (dst) {
mem = (char *)malloc(dlen);
if (mem == NULL) {
if (!mem) {
err(L"w2u: %s: MALLOC FAILED", descr);
return;
}
@ -1673,8 +1673,13 @@ struct pager_layout_testcase_t {
wcstring text = sd.line(0).to_string();
if (text != expected) {
fwprintf(stderr, L"width %zu got <%ls>, expected <%ls>\n", this->width,
text.c_str(), expected.c_str());
fwprintf(stderr, L"width %zu got %d<%ls>, expected %d<%ls>\n", this->width,
text.length(), text.c_str(), expected.length(), expected.c_str());
for (size_t i = 0; i < std::max(text.length(), expected.length()); i++) {
fwprintf(stderr, L"i %zu got <%lx> expected <%lx>\n", i,
i >= text.length() ? 0xffff : text[i],
i >= expected.length() ? 0xffff : expected[i]);
}
}
do_test(text == expected);
}
@ -4178,7 +4183,7 @@ int main(int argc, char **argv) {
function_init();
builtin_init();
env_init();
misc_init();
reader_init();
// Set default signal handlers, so we can ctrl-C out of this.

View file

@ -2,19 +2,9 @@
#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#if HAVE_NCURSES_H
#include <ncurses.h>
#elif HAVE_NCURSES_CURSES_H
#include <ncurses/curses.h>
#else
#include <curses.h>
#endif
#if HAVE_TERM_H
#include <term.h>
#elif HAVE_NCURSES_TERM_H
@ -33,14 +23,12 @@
#include "input.h"
#include "input_common.h"
#include "io.h"
#include "output.h"
#include "parser.h"
#include "proc.h"
#include "reader.h"
#include "signal.h" // IWYU pragma: keep
#include "wutil.h" // IWYU pragma: keep
#define DEFAULT_TERM L"ansi"
#define MAX_INPUT_FUNCTION_ARGS 20
/// Struct representing a keybinding. Returned by input_get_mappings.
@ -201,11 +189,11 @@ static std::vector<terminfo_mapping_t> terminfo_mappings;
/// List of all terminfo mappings.
static std::vector<terminfo_mapping_t> mappings;
/// Set to one when the input subsytem has been initialized.
static bool is_init = false;
/// Set to true when the input subsytem has been initialized.
bool input_initialized = false;
/// Initialize terminfo.
static void input_terminfo_init();
static void init_input_terminfo();
static wchar_t input_function_args[MAX_INPUT_FUNCTION_ARGS];
static bool input_function_status;
@ -300,87 +288,11 @@ static int interrupt_handler() {
return R_NULL;
}
void update_fish_color_support(void) {
// Detect or infer term256 support. If fish_term256 is set, we respect it;
// otherwise infer it from the TERM variable or use terminfo.
env_var_t fish_term256 = env_get_string(L"fish_term256");
env_var_t term = env_get_string(L"TERM");
bool support_term256 = false; // default to no support
if (!fish_term256.missing_or_empty()) {
support_term256 = from_string<bool>(fish_term256);
debug(2, L"256 color support determined by 'fish_term256'");
} else if (term.find(L"256color") != wcstring::npos) {
// TERM=*256color*: Explicitly supported.
support_term256 = true;
debug(2, L"256 color support enabled for '256color' in TERM");
} else if (term.find(L"xterm") != wcstring::npos) {
// Assume that all xterms are 256, except for OS X SnowLeopard
const env_var_t prog = env_get_string(L"TERM_PROGRAM");
const env_var_t progver = env_get_string(L"TERM_PROGRAM_VERSION");
if (prog == L"Apple_Terminal" && !progver.missing_or_empty()) {
// OS X Lion is version 300+, it has 256 color support
if (strtod(wcs2str(progver), NULL) > 300) {
support_term256 = true;
debug(2, L"256 color support enabled for TERM=xterm + modern Terminal.app");
}
} else {
support_term256 = true;
debug(2, L"256 color support enabled for TERM=xterm");
}
} else if (cur_term != NULL) {
// See if terminfo happens to identify 256 colors
support_term256 = (max_colors >= 256);
debug(2, L"256 color support: using %d colors per terminfo", max_colors);
} else {
debug(2, L"256 color support not enabled (yet)");
}
env_var_t fish_term24bit = env_get_string(L"fish_term24bit");
bool support_term24bit;
if (!fish_term24bit.missing_or_empty()) {
support_term24bit = from_string<bool>(fish_term24bit);
debug(2, L"'fish_term24bit' preference: 24-bit color %s",
support_term24bit ? L"enabled" : L"disabled");
} else {
// We don't attempt to infer term24 bit support yet.
support_term24bit = false;
}
color_support_t support = (support_term256 ? color_support_term256 : 0) |
(support_term24bit ? color_support_term24bit : 0);
output_set_color_support(support);
}
int input_init() {
if (is_init) return 1;
is_init = true;
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
/// initializations for our input subsystem.
void init_input() {
input_common_init(&interrupt_handler);
int err_ret;
if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) {
debug(0, _(L"Could not set up terminal"));
env_var_t term = env_get_string(L"TERM");
if (term.missing_or_empty()) {
debug(0, _(L"TERM environment variable not set"));
} else {
debug(0, _(L"TERM environment variable set to '%ls'"), term.c_str());
debug(0, _(L"Check that this terminal type is supported on this system"));
}
env_set(L"TERM", DEFAULT_TERM, ENV_GLOBAL | ENV_EXPORT);
if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) {
debug(0,
_(L"Could not set up terminal using the fallback terminal type '%ls' - exiting"),
DEFAULT_TERM);
exit_without_destructors(1);
} else {
debug(0, _(L"Using fallback terminal type '%ls'"), DEFAULT_TERM);
}
fputwc(L'\n', stderr);
}
input_terminfo_init();
update_fish_color_support();
init_input_terminfo();
// If we have no keybindings, add a few simple defaults.
if (mapping_list.empty()) {
@ -393,17 +305,14 @@ int input_init() {
input_mapping_add(L"\x5", L"bind");
}
return 1;
input_initialized = true;
return;
}
void input_destroy() {
if (!is_init) return;
is_init = false;
if (!input_initialized) return;
input_initialized = false;
input_common_destroy();
if (del_curterm(cur_term) == ERR) {
debug(0, _(L"Error while closing terminfo"));
}
}
void input_function_push_arg(wchar_t arg) {
@ -678,8 +587,10 @@ bool input_mapping_get(const wcstring &sequence, const wcstring &mode, wcstring_
return result;
}
/// Add all terminfo mappings.
static void input_terminfo_init() {
/// Add all terminfo mappings and cache other terminfo facts we care about.
static void init_input_terminfo() {
assert(curses_initialized);
if (!cur_term) return; // setupterm() failed so we can't referency any key definitions
const terminfo_mapping_t tinfos[] = {
TERMINFO_ADD(key_a1),
TERMINFO_ADD(key_a3),
@ -843,13 +754,12 @@ static void input_terminfo_init() {
bool input_terminfo_get_sequence(const wchar_t *name, wcstring *out_seq) {
ASSERT_IS_MAIN_THREAD();
assert(input_initialized);
CHECK(name, 0);
const char *res = 0;
int err = ENOENT;
CHECK(name, 0);
input_init();
for (size_t i = 0; i < terminfo_mappings.size(); i++) {
const terminfo_mapping_t &m = terminfo_mappings.at(i);
if (!wcscmp(name, m.name)) {
@ -869,7 +779,7 @@ bool input_terminfo_get_sequence(const wchar_t *name, wcstring *out_seq) {
}
bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) {
input_init();
assert(input_initialized);
for (size_t i = 0; i < terminfo_mappings.size(); i++) {
terminfo_mapping_t &m = terminfo_mappings.at(i);
@ -889,11 +799,10 @@ bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) {
}
wcstring_list_t input_terminfo_get_names(bool skip_null) {
assert(input_initialized);
wcstring_list_t result;
result.reserve(terminfo_mappings.size());
input_init();
for (size_t i = 0; i < terminfo_mappings.size(); i++) {
terminfo_mapping_t &m = terminfo_mappings.at(i);

View file

@ -15,11 +15,12 @@
wcstring describe_char(wint_t c);
/// Initialize the terminal by calling setupterm, and set up arrays used by readch to detect escape
/// sequences for special keys.
///
/// Before calling input_init, terminfo is not initialized and MUST not be used.
int input_init();
/// Set to true when the input subsytem has been initialized.
extern bool input_initialized;
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
/// initializations for our input subsystem.
void init_input();
/// free up memory used by terminal functions.
void input_destroy();
@ -101,7 +102,4 @@ wchar_t input_function_get_code(const wcstring &name);
/// Returns a list of all existing input function names.
wcstring_list_t input_function_get_names(void);
/// Updates our idea of whether we support term256 and term24bit.
void update_fish_color_support();
#endif

View file

@ -96,6 +96,7 @@ static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) {
}
static bool write_foreground_color(unsigned char idx) {
if (!cur_term) return false;
if (set_a_foreground && set_a_foreground[0]) {
return write_color_escape(set_a_foreground, idx, true);
} else if (set_foreground && set_foreground[0]) {
@ -105,6 +106,7 @@ static bool write_foreground_color(unsigned char idx) {
}
static bool write_background_color(unsigned char idx) {
if (!cur_term) return false;
if (set_a_background && set_a_background[0]) {
return write_color_escape(set_a_background, idx, false);
} else if (set_background && set_background[0]) {
@ -115,6 +117,7 @@ static bool write_background_color(unsigned char idx) {
// Exported for builtin_set_color's usage only.
bool write_color(rgb_color_t color, bool is_fg) {
if (!cur_term) return false;
bool supports_term24bit =
static_cast<bool>(output_get_color_support() & color_support_term24bit);
if (!supports_term24bit || !color.is_rgb()) {
@ -168,6 +171,7 @@ void set_color(rgb_color_t c, rgb_color_t c2) {
debug(3, "set_color %ls : %ls\n", tmp.c_str(), tmp2.c_str());
#endif
ASSERT_IS_MAIN_THREAD();
if (!cur_term) return;
const rgb_color_t normal = rgb_color_t::normal();
static rgb_color_t last_color = rgb_color_t::normal();

View file

@ -1539,10 +1539,10 @@ static bool check_for_orphaned_process(unsigned long loop_count, pid_t shell_pgi
/// Initialize data for interactive use.
static void reader_interactive_init() {
assert(input_initialized);
// See if we are running interactively.
pid_t shell_pgid;
input_init();
kill_init();
shell_pgid = getpgrp();

View file

@ -7,6 +7,7 @@
// IWYU pragma: no_include <cstddef>
#include "config.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -552,7 +553,7 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y) {
// Note that this is required to avoid some visual glitches in iTerm (issue #1448).
bool use_multi =
multi_str != NULL && multi_str[0] != '\0' && abs(x_steps) * strlen(str) > strlen(multi_str);
if (use_multi) {
if (use_multi && cur_term) {
char *multi_param = tparm(multi_str, abs(x_steps));
writembs(multi_param);
} else {
@ -900,7 +901,10 @@ static void s_update(screen_t *scr, const wchar_t *left_prompt, const wchar_t *r
}
/// Returns true if we are using a dumb terminal.
static bool is_dumb(void) { return !cursor_up || !cursor_down || !cursor_left || !cursor_right; }
static bool is_dumb(void) {
if (!cur_term) return true;
return !cursor_up || !cursor_down || !cursor_left || !cursor_right;
}
struct screen_layout_t {
// The left prompt that we're going to use.
@ -1232,7 +1236,7 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) {
// We do `>` rather than `>=` because the code below might require one extra space.
if (screen_width > non_space_width) {
bool justgrey = true;
if (enter_dim_mode) {
if (cur_term && enter_dim_mode) {
std::string dim = tparm(enter_dim_mode);
if (!dim.empty()) {
// Use dim if they have it, so the color will be based on their actual normal
@ -1241,7 +1245,7 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) {
justgrey = false;
}
}
if (justgrey && set_a_foreground) {
if (cur_term && justgrey && set_a_foreground) {
if (max_colors >= 238) {
// draw the string in a particular grey
abandon_line_string.append(str2wcstring(tparm(set_a_foreground, 237)));
@ -1254,15 +1258,18 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) {
abandon_line_string.append(str2wcstring(tparm(set_a_foreground, 0)));
}
}
abandon_line_string.push_back(omitted_newline_char);
if (exit_attribute_mode) {
if (cur_term && exit_attribute_mode) {
abandon_line_string.append(
str2wcstring(tparm(exit_attribute_mode))); // normal text ANSI escape sequence
}
int newline_glitch_width = term_has_xn ? 0 : 1;
abandon_line_string.append(screen_width - non_space_width - newline_glitch_width, L' ');
}
abandon_line_string.push_back(L'\r');
abandon_line_string.push_back(omitted_newline_char);
// Now we are certainly on a new line. But we may have dropped the omitted newline char on

View file

@ -14,6 +14,7 @@
#include <sys/stat.h>
#include <algorithm>
#include <cstddef>
#include <map>
#include <memory>
#include <set>

View file

@ -220,7 +220,7 @@ static void handle_hup(int sig, siginfo_t *info, void *context) {
}
/// Handle sigterm. The only thing we do is restore the front process ID, then die.
static void handle_term(int sig, siginfo_t *info, void *context) {
static void handle_sigterm(int sig, siginfo_t *info, void *context) {
UNUSED(sig);
UNUSED(info);
UNUSED(context);
@ -286,7 +286,7 @@ static void set_interactive_handlers() {
sigaction(SIGINT, &act, 0);
// SIGTERM restores the terminal controlling process before dying.
act.sa_sigaction = &handle_term;
act.sa_sigaction = &handle_sigterm;
act.sa_flags = SA_SIGINFO;
sigaction(SIGTERM, &act, 0);