Switch to the terminfo crate

This allows us to get the terminfo information without linking against curses.

That means we can get by without a bunch of awkward C-API trickery.

There is no global "cur_term" kept by a library for us that we need to invalidate.

Note that it still requires a "unhashed terminfo database", and I don't know how well it handles termcap.

I am not actually sure if there are systems that *can't* have terminfo, everything I looked at
has the ncurses terminfo available to install at least.
This commit is contained in:
Fabian Boehm 2024-01-25 21:39:45 +01:00
parent 2c2ab0c1fa
commit fc794bab4c
10 changed files with 260 additions and 424 deletions

View File

@ -25,7 +25,6 @@ endif()
include(GNUInstallDirs)
add_definitions(-D_UNICODE=1)
include(cmake/ConfigureChecks.cmake)
include(cmake/gettext.cmake)
# Set up PCRE2

84
Cargo.lock generated
View File

@ -117,9 +117,16 @@ dependencies = [
"rand_pcg",
"rsconf",
"serial_test",
"terminfo",
"widestring",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.12"
@ -219,6 +226,18 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.25.1"
@ -231,6 +250,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.17"
@ -291,6 +320,44 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "pkg-config"
version = "0.3.29"
@ -414,6 +481,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smallvec"
version = "1.13.1"
@ -442,6 +515,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "terminfo"
version = "0.8.0"
source = "git+https://github.com/meh/rust-terminfo?rev=870327dd79beaecf50db09a15931d5ee1de2a24d#870327dd79beaecf50db09a15931d5ee1de2a24d"
dependencies = [
"fnv",
"nom",
"phf",
"phf_codegen",
]
[[package]]
name = "thread_local"
version = "1.1.7"

View File

@ -35,6 +35,7 @@ once_cell = "1.17.0"
rand = { version = "0.8.5", features = ["small_rng"] }
widestring = "1.0.2"
git-version = "0.3"
terminfo = { git = "https://github.com/meh/rust-terminfo", rev = "870327dd79beaecf50db09a15931d5ee1de2a24d" }
[dev-dependencies]
rand_pcg = "0.3.1"

View File

@ -3,7 +3,7 @@
use rsconf::{LinkType, Target};
use std::env;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
fn main() {
setup_paths();
@ -38,48 +38,6 @@ fn main() {
// Keep verbose mode on until we've ironed out rust build script stuff
target.set_verbose(true);
detect_cfgs(&mut target);
// Handle case where CMake has found curses for us and where we have to find it ourselves.
rsconf::rebuild_if_env_changed("CURSES_LIBRARY_LIST");
let curses_lib_list = env::var("CURSES_LIBRARY_LIST");
let curses_libraries = if let Ok(lib_path_list) = curses_lib_list.as_ref() {
let lib_paths = lib_path_list.split(',').filter(|s| !s.is_empty());
let curses_libnames: Vec<_> = lib_paths
.map(|libpath| {
let stem = Path::new(libpath).file_stem().unwrap().to_str().unwrap();
// Ubuntu-32bit-fetched-pcre2's ncurses doesn't have the "lib" prefix.
stem.strip_prefix("lib").unwrap_or(stem)
})
.collect();
// We don't need to test the libs because presumably CMake already did
rsconf::link_libraries(&curses_libnames, LinkType::Default);
curses_libnames
} else {
let mut curses_libraries = vec![];
let libs = ["ncurses", "curses"];
for lib in libs {
if target.has_library(lib) && target.has_symbol("setupterm", lib) {
rsconf::link_library(lib, LinkType::Default);
curses_libraries.push(lib);
break;
}
}
if curses_libraries.is_empty() {
panic!("Could not locate a compatible curses library (tried {libs:?})");
}
curses_libraries
};
for lib in curses_libraries {
if target.has_symbol("_nc_cur_term", lib) {
rsconf::enable_cfg("have_nc_cur_term");
if target.has_symbol("cur_term", lib) {
rsconf::warn!("curses provides both cur_term and _nc_cur_term");
}
break;
}
}
}
/// Check target system support for certain functionality dynamically when the build is invoked,

View File

@ -1,31 +0,0 @@
# The following defines affect the environment configuration tests are run in:
# CMAKE_REQUIRED_DEFINITIONS, CMAKE_REQUIRED_FLAGS, CMAKE_REQUIRED_LIBRARIES,
# and CMAKE_REQUIRED_INCLUDES
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE=1)
# Try using CMake's own logic to locate curses/ncurses
find_package(Curses)
if(NOT ${CURSES_FOUND})
# CMake has trouble finding platform-specific system libraries
# installed to multiarch paths (e.g. /usr/lib/x86_64-linux-gnu)
# if not symlinked or passed in as a manual define.
message("Falling back to pkg-config for (n)curses detection")
include(FindPkgConfig)
pkg_search_module(CURSES REQUIRED ncurses curses)
set(CURSES_CURSES_LIBRARY ${CURSES_LIBRARIES})
set(CURSES_LIBRARY ${CURSES_LIBRARIES})
endif()
# Fix undefined reference to tparm on RHEL 6 and potentially others
# If curses is found via CMake, it also links against tinfo if it exists. But if we use our
# fallback pkg-config logic above, we need to do this manually.
find_library(CURSES_TINFO tinfo)
if (CURSES_TINFO)
set(CURSES_LIBRARY ${CURSES_LIBRARY} ${CURSES_TINFO})
else()
# on NetBSD, libtinfo has a longer name (libterminfo)
find_library(CURSES_TINFO terminfo)
if (CURSES_TINFO)
set(CURSES_LIBRARY ${CURSES_LIBRARY} ${CURSES_TINFO})
endif()
endif()

View File

@ -8,7 +8,7 @@
//! This is intentionally very bare bones and only implements the subset of curses functionality
//! used by fish
use self::sys::*;
use crate::common::ToCString;
use std::ffi::{CStr, CString};
use std::sync::Arc;
use std::sync::Mutex;
@ -36,85 +36,6 @@ pub fn term() -> Option<Arc<Term>> {
.map(Arc::clone)
}
/// Convert a nul-terminated pointer, which must not be itself null, to a CString.
fn ptr_to_cstr(ptr: *const libc::c_char) -> CString {
assert!(!ptr.is_null());
unsafe { CStr::from_ptr(ptr).to_owned() }
}
/// Convert a nul-terminated pointer to a CString, or None if the pointer is null.
fn try_ptr_to_cstr(ptr: *const libc::c_char) -> Option<CString> {
if ptr.is_null() {
None
} else {
Some(ptr_to_cstr(ptr))
}
}
/// Private module exposing system curses ffi.
mod sys {
pub const OK: i32 = 0;
/// tputs callback argument type and the callback type itself.
/// N.B. The C++ had a check for TPUTS_USES_INT_ARG for the first parameter of tputs
/// which was to support OpenIndiana, which used a char.
pub type tputs_arg = libc::c_int;
pub type putc_t = extern "C" fn(tputs_arg) -> libc::c_int;
extern "C" {
#[cfg(not(have_nc_cur_term))]
pub static mut cur_term: *const core::ffi::c_void;
#[cfg(have_nc_cur_term)]
pub fn _nc_cur_term() -> *const core::ffi::c_void;
/// setupterm(3) is a low-level call to begin doing any sort of `term.h`/`curses.h` work.
/// It's called internally by ncurses's `initscr()` and `newterm()`, but the C++ code called
/// it directly from [`initialize_curses_using_fallbacks()`].
pub fn setupterm(
term: *const libc::c_char,
filedes: libc::c_int,
errret: *mut libc::c_int,
) -> libc::c_int;
/// Frees the `cur_term` TERMINAL pointer.
pub fn del_curterm(term: *const core::ffi::c_void) -> libc::c_int;
/// Sets the `cur_term` TERMINAL pointer.
pub fn set_curterm(term: *const core::ffi::c_void) -> *const core::ffi::c_void;
/// Checks for the presence of a termcap flag identified by the first two characters of
/// `id`.
pub fn tgetflag(id: *const libc::c_char) -> libc::c_int;
/// Checks for the presence and value of a number capability in the termcap/termconf
/// database. A return value of `-1` indicates not found.
pub fn tgetnum(id: *const libc::c_char) -> libc::c_int;
pub fn tgetstr(
id: *const libc::c_char,
area: *mut *mut libc::c_char,
) -> *const libc::c_char;
pub fn tparm(str: *const libc::c_char, ...) -> *const libc::c_char;
pub fn tputs(str: *const libc::c_char, affcnt: libc::c_int, putc: putc_t) -> libc::c_int;
}
}
/// The ncurses `cur_term` TERMINAL pointer.
fn get_curterm() -> *const core::ffi::c_void {
#[cfg(have_nc_cur_term)]
unsafe {
sys::_nc_cur_term()
}
#[cfg(not(have_nc_cur_term))]
unsafe {
sys::cur_term
}
}
pub use sys::tputs_arg as TputsArg;
/// The safe wrapper around curses functionality, initialized by a successful call to [`setup()`]
/// and obtained thereafter by calls to [`term()`].
///
@ -275,157 +196,157 @@ pub struct Term {
impl Term {
/// Initialize a new `Term` instance, prepopulating the values of all the curses string
/// capabilities we care about in the process.
fn new() -> Self {
fn new(db: terminfo::Database) -> Self {
Term {
// String capabilities
enter_bold_mode: get_str_cap("md"),
enter_italics_mode: get_str_cap("ZH"),
exit_italics_mode: get_str_cap("ZR"),
enter_dim_mode: get_str_cap("mh"),
enter_underline_mode: get_str_cap("us"),
exit_underline_mode: get_str_cap("ue"),
enter_reverse_mode: get_str_cap("mr"),
enter_standout_mode: get_str_cap("so"),
exit_standout_mode: get_str_cap("se"),
enter_blink_mode: get_str_cap("mb"),
enter_protected_mode: get_str_cap("mp"),
enter_shadow_mode: get_str_cap("ZM"),
exit_shadow_mode: get_str_cap("ZU"),
enter_secure_mode: get_str_cap("mk"),
enter_alt_charset_mode: get_str_cap("as"),
exit_alt_charset_mode: get_str_cap("ae"),
set_a_foreground: get_str_cap("AF"),
set_foreground: get_str_cap("Sf"),
set_a_background: get_str_cap("AB"),
set_background: get_str_cap("Sb"),
exit_attribute_mode: get_str_cap("me"),
set_title: get_str_cap("ts"),
clear_screen: get_str_cap("cl"),
cursor_up: get_str_cap("up"),
cursor_down: get_str_cap("do"),
cursor_left: get_str_cap("le"),
cursor_right: get_str_cap("nd"),
parm_left_cursor: get_str_cap("LE"),
parm_right_cursor: get_str_cap("RI"),
clr_eol: get_str_cap("ce"),
clr_eos: get_str_cap("cd"),
enter_bold_mode: get_str_cap(&db, "md"),
enter_italics_mode: get_str_cap(&db, "ZH"),
exit_italics_mode: get_str_cap(&db, "ZR"),
enter_dim_mode: get_str_cap(&db, "mh"),
enter_underline_mode: get_str_cap(&db, "us"),
exit_underline_mode: get_str_cap(&db, "ue"),
enter_reverse_mode: get_str_cap(&db, "mr"),
enter_standout_mode: get_str_cap(&db, "so"),
exit_standout_mode: get_str_cap(&db, "se"),
enter_blink_mode: get_str_cap(&db, "mb"),
enter_protected_mode: get_str_cap(&db, "mp"),
enter_shadow_mode: get_str_cap(&db, "ZM"),
exit_shadow_mode: get_str_cap(&db, "ZU"),
enter_secure_mode: get_str_cap(&db, "mk"),
enter_alt_charset_mode: get_str_cap(&db, "as"),
exit_alt_charset_mode: get_str_cap(&db, "ae"),
set_a_foreground: get_str_cap(&db, "AF"),
set_foreground: get_str_cap(&db, "Sf"),
set_a_background: get_str_cap(&db, "AB"),
set_background: get_str_cap(&db, "Sb"),
exit_attribute_mode: get_str_cap(&db, "me"),
set_title: get_str_cap(&db, "ts"),
clear_screen: get_str_cap(&db, "cl"),
cursor_up: get_str_cap(&db, "up"),
cursor_down: get_str_cap(&db, "do"),
cursor_left: get_str_cap(&db, "le"),
cursor_right: get_str_cap(&db, "nd"),
parm_left_cursor: get_str_cap(&db, "LE"),
parm_right_cursor: get_str_cap(&db, "RI"),
clr_eol: get_str_cap(&db, "ce"),
clr_eos: get_str_cap(&db, "cd"),
// Number capabilities
max_colors: get_num_cap("Co"),
init_tabs: get_num_cap("it"),
max_colors: get_num_cap(&db, "Co"),
init_tabs: get_num_cap(&db, "it"),
// Flag/boolean capabilities
eat_newline_glitch: get_flag_cap("xn"),
auto_right_margin: get_flag_cap("am"),
eat_newline_glitch: get_flag_cap(&db, "xn"),
auto_right_margin: get_flag_cap(&db, "am"),
// Keys. See `man terminfo` for these strings.
key_a1: get_str_cap("K1"),
key_a3: get_str_cap("K3"),
key_b2: get_str_cap("K2"),
key_backspace: get_str_cap("kb"),
key_beg: get_str_cap("@1"),
key_btab: get_str_cap("kB"),
key_c1: get_str_cap("K4"),
key_c3: get_str_cap("K5"),
key_cancel: get_str_cap("@2"),
key_catab: get_str_cap("ka"),
key_clear: get_str_cap("kC"),
key_close: get_str_cap("@3"),
key_command: get_str_cap("@4"),
key_copy: get_str_cap("@5"),
key_create: get_str_cap("@6"),
key_ctab: get_str_cap("kt"),
key_dc: get_str_cap("kD"),
key_dl: get_str_cap("kL"),
key_down: get_str_cap("kd"),
key_eic: get_str_cap("kM"),
key_end: get_str_cap("@7"),
key_enter: get_str_cap("@8"),
key_eol: get_str_cap("kE"),
key_eos: get_str_cap("kS"),
key_exit: get_str_cap("@9"),
key_f0: get_str_cap("k0"),
key_f1: get_str_cap("k1"),
key_f10: get_str_cap("k;"),
key_f11: get_str_cap("F1"),
key_f12: get_str_cap("F2"),
key_f13: get_str_cap("F3"),
key_f14: get_str_cap("F4"),
key_f15: get_str_cap("F5"),
key_f16: get_str_cap("F6"),
key_f17: get_str_cap("F7"),
key_f18: get_str_cap("F8"),
key_f19: get_str_cap("F9"),
key_f2: get_str_cap("k2"),
key_f20: get_str_cap("FA"),
key_a1: get_str_cap(&db, "K1"),
key_a3: get_str_cap(&db, "K3"),
key_b2: get_str_cap(&db, "K2"),
key_backspace: get_str_cap(&db, "kb"),
key_beg: get_str_cap(&db, "@1"),
key_btab: get_str_cap(&db, "kB"),
key_c1: get_str_cap(&db, "K4"),
key_c3: get_str_cap(&db, "K5"),
key_cancel: get_str_cap(&db, "@2"),
key_catab: get_str_cap(&db, "ka"),
key_clear: get_str_cap(&db, "kC"),
key_close: get_str_cap(&db, "@3"),
key_command: get_str_cap(&db, "@4"),
key_copy: get_str_cap(&db, "@5"),
key_create: get_str_cap(&db, "@6"),
key_ctab: get_str_cap(&db, "kt"),
key_dc: get_str_cap(&db, "kD"),
key_dl: get_str_cap(&db, "kL"),
key_down: get_str_cap(&db, "kd"),
key_eic: get_str_cap(&db, "kM"),
key_end: get_str_cap(&db, "@7"),
key_enter: get_str_cap(&db, "@8"),
key_eol: get_str_cap(&db, "kE"),
key_eos: get_str_cap(&db, "kS"),
key_exit: get_str_cap(&db, "@9"),
key_f0: get_str_cap(&db, "k0"),
key_f1: get_str_cap(&db, "k1"),
key_f10: get_str_cap(&db, "k;"),
key_f11: get_str_cap(&db, "F1"),
key_f12: get_str_cap(&db, "F2"),
key_f13: get_str_cap(&db, "F3"),
key_f14: get_str_cap(&db, "F4"),
key_f15: get_str_cap(&db, "F5"),
key_f16: get_str_cap(&db, "F6"),
key_f17: get_str_cap(&db, "F7"),
key_f18: get_str_cap(&db, "F8"),
key_f19: get_str_cap(&db, "F9"),
key_f2: get_str_cap(&db, "k2"),
key_f20: get_str_cap(&db, "FA"),
// Note key_f21 through key_f63 are available but no actual keyboard supports them.
key_f3: get_str_cap("k3"),
key_f4: get_str_cap("k4"),
key_f5: get_str_cap("k5"),
key_f6: get_str_cap("k6"),
key_f7: get_str_cap("k7"),
key_f8: get_str_cap("k8"),
key_f9: get_str_cap("k9"),
key_find: get_str_cap("@0"),
key_help: get_str_cap("%1"),
key_home: get_str_cap("kh"),
key_ic: get_str_cap("kI"),
key_il: get_str_cap("kA"),
key_left: get_str_cap("kl"),
key_ll: get_str_cap("kH"),
key_mark: get_str_cap("%2"),
key_message: get_str_cap("%3"),
key_move: get_str_cap("%4"),
key_next: get_str_cap("%5"),
key_npage: get_str_cap("kN"),
key_open: get_str_cap("%6"),
key_options: get_str_cap("%7"),
key_ppage: get_str_cap("kP"),
key_previous: get_str_cap("%8"),
key_print: get_str_cap("%9"),
key_redo: get_str_cap("%0"),
key_reference: get_str_cap("&1"),
key_refresh: get_str_cap("&2"),
key_replace: get_str_cap("&3"),
key_restart: get_str_cap("&4"),
key_resume: get_str_cap("&5"),
key_right: get_str_cap("kr"),
key_save: get_str_cap("&6"),
key_sbeg: get_str_cap("&9"),
key_scancel: get_str_cap("&0"),
key_scommand: get_str_cap("*1"),
key_scopy: get_str_cap("*2"),
key_screate: get_str_cap("*3"),
key_sdc: get_str_cap("*4"),
key_sdl: get_str_cap("*5"),
key_select: get_str_cap("*6"),
key_send: get_str_cap("*7"),
key_seol: get_str_cap("*8"),
key_sexit: get_str_cap("*9"),
key_sf: get_str_cap("kF"),
key_sfind: get_str_cap("*0"),
key_shelp: get_str_cap("#1"),
key_shome: get_str_cap("#2"),
key_sic: get_str_cap("#3"),
key_sleft: get_str_cap("#4"),
key_smessage: get_str_cap("%a"),
key_smove: get_str_cap("%b"),
key_snext: get_str_cap("%c"),
key_soptions: get_str_cap("%d"),
key_sprevious: get_str_cap("%e"),
key_sprint: get_str_cap("%f"),
key_sr: get_str_cap("kR"),
key_sredo: get_str_cap("%g"),
key_sreplace: get_str_cap("%h"),
key_sright: get_str_cap("%i"),
key_srsume: get_str_cap("%j"),
key_ssave: get_str_cap("!1"),
key_ssuspend: get_str_cap("!2"),
key_stab: get_str_cap("kT"),
key_sundo: get_str_cap("!3"),
key_suspend: get_str_cap("&7"),
key_undo: get_str_cap("&8"),
key_up: get_str_cap("ku"),
key_f3: get_str_cap(&db, "k3"),
key_f4: get_str_cap(&db, "k4"),
key_f5: get_str_cap(&db, "k5"),
key_f6: get_str_cap(&db, "k6"),
key_f7: get_str_cap(&db, "k7"),
key_f8: get_str_cap(&db, "k8"),
key_f9: get_str_cap(&db, "k9"),
key_find: get_str_cap(&db, "@0"),
key_help: get_str_cap(&db, "%1"),
key_home: get_str_cap(&db, "kh"),
key_ic: get_str_cap(&db, "kI"),
key_il: get_str_cap(&db, "kA"),
key_left: get_str_cap(&db, "kl"),
key_ll: get_str_cap(&db, "kH"),
key_mark: get_str_cap(&db, "%2"),
key_message: get_str_cap(&db, "%3"),
key_move: get_str_cap(&db, "%4"),
key_next: get_str_cap(&db, "%5"),
key_npage: get_str_cap(&db, "kN"),
key_open: get_str_cap(&db, "%6"),
key_options: get_str_cap(&db, "%7"),
key_ppage: get_str_cap(&db, "kP"),
key_previous: get_str_cap(&db, "%8"),
key_print: get_str_cap(&db, "%9"),
key_redo: get_str_cap(&db, "%0"),
key_reference: get_str_cap(&db, "&1"),
key_refresh: get_str_cap(&db, "&2"),
key_replace: get_str_cap(&db, "&3"),
key_restart: get_str_cap(&db, "&4"),
key_resume: get_str_cap(&db, "&5"),
key_right: get_str_cap(&db, "kr"),
key_save: get_str_cap(&db, "&6"),
key_sbeg: get_str_cap(&db, "&9"),
key_scancel: get_str_cap(&db, "&0"),
key_scommand: get_str_cap(&db, "*1"),
key_scopy: get_str_cap(&db, "*2"),
key_screate: get_str_cap(&db, "*3"),
key_sdc: get_str_cap(&db, "*4"),
key_sdl: get_str_cap(&db, "*5"),
key_select: get_str_cap(&db, "*6"),
key_send: get_str_cap(&db, "*7"),
key_seol: get_str_cap(&db, "*8"),
key_sexit: get_str_cap(&db, "*9"),
key_sf: get_str_cap(&db, "kF"),
key_sfind: get_str_cap(&db, "*0"),
key_shelp: get_str_cap(&db, "#1"),
key_shome: get_str_cap(&db, "#2"),
key_sic: get_str_cap(&db, "#3"),
key_sleft: get_str_cap(&db, "#4"),
key_smessage: get_str_cap(&db, "%a"),
key_smove: get_str_cap(&db, "%b"),
key_snext: get_str_cap(&db, "%c"),
key_soptions: get_str_cap(&db, "%d"),
key_sprevious: get_str_cap(&db, "%e"),
key_sprint: get_str_cap(&db, "%f"),
key_sr: get_str_cap(&db, "kR"),
key_sredo: get_str_cap(&db, "%g"),
key_sreplace: get_str_cap(&db, "%h"),
key_sright: get_str_cap(&db, "%i"),
key_srsume: get_str_cap(&db, "%j"),
key_ssave: get_str_cap(&db, "!1"),
key_ssuspend: get_str_cap(&db, "!2"),
key_stab: get_str_cap(&db, "kT"),
key_sundo: get_str_cap(&db, "!3"),
key_suspend: get_str_cap(&db, "&7"),
key_undo: get_str_cap(&db, "&8"),
key_up: get_str_cap(&db, "ku"),
}
}
}
@ -441,7 +362,7 @@ impl Term {
/// error output to stderr in case of failure.
///
/// Any existing references from `curses::term()` will be invalidated by this call!
pub fn setup<F>(term: Option<&CStr>, fd: i32, configure: F) -> Option<Arc<Term>>
pub fn setup<F>(term: Option<&CStr>, configure: F) -> Option<Arc<Term>>
where
F: Fn(&mut Term),
{
@ -449,25 +370,19 @@ where
// curses itself. We might split this to another lock in the future.
let mut global_term = TERM.lock().expect("Mutex poisoned!");
let result = unsafe {
// If cur_term is already initialized for a different $TERM value, calling setupterm() again
// will leak memory. Call del_curterm() first to free previously allocated resources.
let _ = sys::del_curterm(get_curterm());
let mut err = 0;
if let Some(term) = term {
sys::setupterm(term.as_ptr(), fd, &mut err)
} else {
sys::setupterm(core::ptr::null(), fd, &mut err)
}
let res = if let Some(term) = term {
terminfo::Database::from_name(term.to_str().unwrap())
} else {
// For historical reasons getting "None" means to get it from the environment.
terminfo::Database::from_env()
};
// Safely store the new Term instance or replace the old one. We have the lock so it's safe to
// drop the old TERM value and have its refcount decremented - no one will be cloning it.
if result == sys::OK {
if let Ok(result) = res {
// Create a new `Term` instance, prepopulate the capabilities we care about, and allow the
// caller to override any as needed.
let mut term = Term::new();
let mut term = Term::new(result);
(configure)(&mut term);
let term = Arc::new(term);
@ -479,90 +394,43 @@ where
}
}
/// Resets the curses `cur_term` TERMINAL pointer. Subsequent calls to [`curses::term()`](term())
/// will return `None`.
pub fn reset() {
let mut term = TERM.lock().expect("Mutex poisoned!");
if term.is_some() {
unsafe {
// Ignore the result of del_curterm() as the only documented error is that
// `cur_term` was already null.
let _ = sys::del_curterm(get_curterm());
let _ = sys::set_curterm(core::ptr::null());
}
*term = None;
}
}
/// Return a nonempty String capability from termcap, or None if missing or empty.
/// Panics if the given code string does not contain exactly two bytes.
fn get_str_cap(code: &str) -> Option<CString> {
let code = to_cstr_code(code);
// termcap spec says nul is not allowed in terminal sequences and must be encoded;
// so the terminating NUL is the end of the string.
let tstr = unsafe { sys::tgetstr(code.as_ptr(), core::ptr::null_mut()) };
let result = try_ptr_to_cstr(tstr);
// Paranoia: do not return empty strings.
if let Some(s) = &result {
if s.as_bytes().is_empty() {
return None;
}
}
result
fn get_str_cap(db: &terminfo::Database, code: &str) -> Option<CString> {
db.raw(code).map(|cap| match cap {
terminfo::Value::True => "1".to_string().as_bytes().to_cstring(),
terminfo::Value::Number(n) => n.to_string().as_bytes().to_cstring(),
terminfo::Value::String(s) => s.clone().to_cstring(),
})
}
/// Return a number capability from termcap, or None if missing.
/// Panics if the given code string does not contain exactly two bytes.
fn get_num_cap(code: &str) -> Option<usize> {
let code = to_cstr_code(code);
match unsafe { sys::tgetnum(code.as_ptr()) } {
-1 => None,
n => Some(usize::try_from(n).unwrap()),
fn get_num_cap(db: &terminfo::Database, code: &str) -> Option<usize> {
match db.raw(code) {
Some(terminfo::Value::Number(n)) if *n >= 0 => Some(*n as usize),
_ => None,
}
}
/// Return a flag capability from termcap, or false if missing.
/// Panics if the given code string does not contain exactly two bytes.
fn get_flag_cap(code: &str) -> bool {
let code = to_cstr_code(code);
unsafe { sys::tgetflag(code.as_ptr()) != 0 }
}
/// `code` is the two-digit termcap code. See termcap(5) for a reference.
/// Panics if anything other than a two-ascii-character `code` is passed into the function.
const fn to_cstr_code(code: &str) -> [libc::c_char; 3] {
use libc::c_char;
let code = code.as_bytes();
if code.len() != 2 {
panic!("Invalid termcap code provided");
}
[code[0] as c_char, code[1] as c_char, b'\0' as c_char]
fn get_flag_cap(db: &terminfo::Database, code: &str) -> bool {
db.raw(code)
.map(|cap| matches!(cap, terminfo::Value::True))
.unwrap_or(false)
}
/// Covers over tparm().
pub fn tparm0(cap: &CStr) -> Option<CString> {
// Take the lock because tparm races with del_curterm, etc.
let _term: std::sync::MutexGuard<Option<Arc<Term>>> = TERM.lock().unwrap();
assert!(!cap.to_bytes().is_empty());
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
// Safety: we're trusting tparm here.
unsafe { try_ptr_to_cstr(tparm(cap_ptr)) }
let cap = cap.to_bytes();
terminfo::expand!(cap).ok().map(|x| x.to_cstring())
}
/// Covers over tparm().
pub fn tparm1(cap: &CStr, param1: i32) -> Option<CString> {
// Take the lock because tparm races with del_curterm, etc.
let _term: std::sync::MutexGuard<Option<Arc<Term>>> = TERM.lock().unwrap();
assert!(!cap.to_bytes().is_empty());
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
// Safety: we're trusting tparm here.
unsafe { try_ptr_to_cstr(tparm(cap_ptr, param1 as libc::c_int)) }
}
/// Wrapper over tputs.
/// The caller is responsible for synchronization.
pub fn tputs(str: &CStr, affcnt: libc::c_int, putc: sys::putc_t) -> libc::c_int {
let str_ptr = str.as_ptr() as *mut libc::c_char;
// Safety: we're trusting tputs here.
unsafe { sys::tputs(str_ptr, affcnt, putc) }
let cap = cap.to_bytes();
terminfo::expand!(cap; param1).ok().map(|x| x.to_cstring())
}

View File

@ -506,10 +506,8 @@ fn initialize_curses_using_fallbacks(vars: &EnvStack) {
// `term` here is one of our hard-coded strings above; we can unwrap because we can
// guarantee it doesn't contain any interior NULs.
let term_cstr = CString::new(term).unwrap();
let success = curses::setup(Some(&term_cstr), libc::STDOUT_FILENO, |term| {
apply_term_hacks(vars, term)
})
.is_some();
let success =
curses::setup(Some(&term_cstr), |term| apply_term_hacks(vars, term)).is_some();
if is_interactive_session() {
if success {
FLOG!(warning, wgettext!("Using fallback terminal type"), term);
@ -640,11 +638,7 @@ fn init_curses(vars: &EnvStack) {
}
}
if curses::setup(None, libc::STDOUT_FILENO, |term| {
apply_term_hacks(vars, term)
})
.is_none()
{
if curses::setup(None, |term| apply_term_hacks(vars, term)).is_none() {
if is_interactive_session() {
let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string());
FLOG!(warning, wgettext!("Could not set up terminal."));

View File

@ -5,7 +5,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <term.h>
#include <unistd.h>
#define UNUSED(x) (void)(x)

View File

@ -1,6 +1,6 @@
// Generic output functions.
use crate::color::RgbColor;
use crate::common::{self, assert_is_locked, wcs2string_appending};
use crate::common::{self, wcs2string_appending};
use crate::curses::{self, tparm1, Term};
use crate::env::EnvVar;
use crate::wchar::prelude::*;
@ -10,7 +10,6 @@ use std::ffi::CStr;
use std::io::{Result, Write};
use std::os::fd::RawFd;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Mutex;
bitflags! {
#[derive(Copy, Clone, Default)]
@ -423,35 +422,13 @@ impl Write for Outputter {
}
}
// tputs accepts a function pointer that receives an int only.
// Use the following lock to redirect it to the proper outputter.
// Note we can't use an owning Mutex because the tputs_writer must access it and Mutex is not
// recursive.
static TPUTS_RECEIVER_LOCK: Mutex<()> = Mutex::new(());
static mut TPUTS_RECEIVER: *mut Outputter = std::ptr::null_mut();
extern "C" fn tputs_writer(b: curses::TputsArg) -> libc::c_int {
// Safety: we hold the lock.
assert_is_locked!(&TPUTS_RECEIVER_LOCK);
let receiver = unsafe { TPUTS_RECEIVER.as_mut().expect("null TPUTS_RECEIVER") };
receiver.push(b as u8);
0
}
impl Outputter {
/// Emit a terminfo string, like tputs.
/// affcnt (number of lines affected) is assumed to be 1, i.e. not applicable.
pub fn tputs(&mut self, str: &CStr) {
let affcnt = 1;
// Acquire the lock, set the receiver, and call tputs.
let _guard = TPUTS_RECEIVER_LOCK.lock().unwrap();
// Safety: we hold the lock.
let saved_recv = unsafe { TPUTS_RECEIVER };
unsafe { TPUTS_RECEIVER = self as *mut Outputter };
self.begin_buffering();
let _ = curses::tputs(str, affcnt, tputs_writer);
let _ = self.write(str.to_bytes());
self.end_buffering();
unsafe { TPUTS_RECEIVER = saved_recv };
}
/// Convenience cover over tputs, in recognition of the fact that our Term has Optional fields.

View File

@ -214,25 +214,12 @@ pub fn spawn<F: FnOnce() + Send + 'static>(callback: F) -> bool {
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
pub fn asan_maybe_exit(code: i32) {
if cfg!(feature = "asan") {
asan_before_exit();
unsafe {
libc::exit(code);
}
}
}
/// When running under ASAN, free up some allocations that would normally have been left for the OS
/// to reclaim to avoid some false positive LSAN reports.
///
/// This function is always defined but is a no-op if not running under ASAN. This is to make it
/// more ergonomic to call it in general and also makes it possible to call it via ffi at all.
pub fn asan_before_exit() {
if cfg!(feature = "asan") && !is_forked_child() {
// Free ncurses terminal state
crate::curses::reset();
}
}
/// Data shared between the thread pool [`ThreadPool`] and worker threads [`WorkerThread`].
#[derive(Default)]
struct ThreadPoolProtected {