// Support for exposing the terminal size. #include "termsize.h" #include "maybe.h" #include "parser.h" #include "wcstringutil.h" #include "wutil.h" #ifdef HAVE_WINSIZE #include #endif // A counter which is incremented every SIGWINCH, or when the tty is otherwise invalidated. static volatile uint32_t s_tty_termsize_gen_count{0}; /// \return a termsize from ioctl, or none on error or if not supported. static maybe_t read_termsize_from_tty() { maybe_t result{}; #ifdef HAVE_WINSIZE struct winsize winsize = {0, 0, 0, 0}; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) >= 0) { // 0 values are unusable, fall back to the default instead. if (winsize.ws_col == 0) { FLOGF(term_support, L"Terminal has 0 columns, falling back to default width"); winsize.ws_col = termsize_t::DEFAULT_WIDTH; } if (winsize.ws_row == 0) { FLOGF(term_support, L"Terminal has 0 rows, falling back to default height"); winsize.ws_row = termsize_t::DEFAULT_HEIGHT; } result = termsize_t(winsize.ws_col, winsize.ws_row); } #endif return result; } // static termsize_container_t &termsize_container_t::shared() { // Heap-allocated to avoid runtime dtor registration. static auto *res = new termsize_container_t(read_termsize_from_tty); return *res; } termsize_t termsize_container_t::data_t::current() const { // This encapsulates our ordering logic. If we have a termsize from a tty, use it; otherwise use // what we have seen from the environment. if (this->last_from_tty) return *this->last_from_tty; if (this->last_from_env) return *this->last_from_env; return termsize_t::defaults(); } void termsize_container_t::data_t::mark_override_from_env(termsize_t ts) { // Here we pretend to have an up-to-date tty value so that we will prefer the environment value. this->last_from_env = ts; this->last_from_tty.reset(); this->last_tty_gen_count = s_tty_termsize_gen_count; } termsize_t termsize_container_t::last() const { return this->data_.acquire()->current(); } termsize_t termsize_container_t::updating(parser_t &parser) { termsize_t new_size = termsize_t::defaults(); termsize_t prev_size = termsize_t::defaults(); // Take the lock in a local region. // Capture the size before and the new size. { auto data = data_.acquire(); prev_size = data->current(); // Critical read of signal-owned variable. // This must happen before the TIOCGWINSZ ioctl. const uint32_t tty_gen_count = s_tty_termsize_gen_count; if (data->last_tty_gen_count != tty_gen_count) { // Our idea of the size of the terminal may be stale. // Apply any updates. data->last_tty_gen_count = tty_gen_count; data->last_from_tty = this->tty_size_reader_(); } new_size = data->current(); } // Announce any updates. if (new_size != prev_size) set_columns_lines_vars(new_size, parser); return new_size; } void termsize_container_t::set_columns_lines_vars(termsize_t val, parser_t &parser) { const bool saved = setting_env_vars_; setting_env_vars_ = true; parser.set_var_and_fire(L"COLUMNS", ENV_GLOBAL, to_string(val.width)); parser.set_var_and_fire(L"LINES", ENV_GLOBAL, to_string(val.height)); setting_env_vars_ = saved; } /// Convert an environment variable to an int, or return a default value. /// The int must be >0 and &var, int def) { if (var.has_value() && !var->empty()) { errno = 0; int proposed = fish_wcstoi(var->as_string().c_str()); if (errno == 0 && proposed > 0 && proposed <= USHRT_MAX) { return proposed; } } return def; } termsize_t termsize_container_t::initialize(const environment_t &vars) { termsize_t new_termsize{ var_to_int_or(vars.get(L"COLUMNS", ENV_GLOBAL), -1), var_to_int_or(vars.get(L"LINES", ENV_GLOBAL), -1), }; auto data = data_.acquire(); if (new_termsize.width > 0 && new_termsize.height > 0) { data->mark_override_from_env(new_termsize); } else { data->last_tty_gen_count = s_tty_termsize_gen_count; data->last_from_tty = this->tty_size_reader_(); } return data->current(); } void termsize_container_t::handle_columns_lines_var_change(const environment_t &vars) { // Do nothing if we are the ones setting it. if (setting_env_vars_) return; // Construct a new termsize from COLUMNS and LINES, then set it in our data. termsize_t new_termsize{ var_to_int_or(vars.get(L"COLUMNS", ENV_GLOBAL), termsize_t::DEFAULT_WIDTH), var_to_int_or(vars.get(L"LINES", ENV_GLOBAL), termsize_t::DEFAULT_HEIGHT), }; // Store our termsize as an environment override. data_.acquire()->mark_override_from_env(new_termsize); } // static void termsize_container_t::handle_winch() { s_tty_termsize_gen_count += 1; } // static void termsize_container_t::invalidate_tty() { s_tty_termsize_gen_count += 1; }