From fc794bab4cc8dc0f0cf6e4e8de95f99df8fa1b81 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Thu, 25 Jan 2024 21:39:45 +0100 Subject: [PATCH] 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. --- CMakeLists.txt | 1 - Cargo.lock | 84 +++++++ Cargo.toml | 1 + build.rs | 44 +--- cmake/ConfigureChecks.cmake | 31 --- src/curses.rs | 470 +++++++++++++----------------------- src/env_dispatch.rs | 12 +- src/libc.c | 1 - src/output.rs | 27 +-- src/threads.rs | 13 - 10 files changed, 260 insertions(+), 424 deletions(-) delete mode 100644 cmake/ConfigureChecks.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 341f50bf1..17d4d24fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,6 @@ endif() include(GNUInstallDirs) add_definitions(-D_UNICODE=1) -include(cmake/ConfigureChecks.cmake) include(cmake/gettext.cmake) # Set up PCRE2 diff --git a/Cargo.lock b/Cargo.lock index 3555526ca..e9262793b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 77da8f73b..fec5ae7c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/build.rs b/build.rs index b387f5696..cb55a0d8e 100644 --- a/build.rs +++ b/build.rs @@ -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, diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake deleted file mode 100644 index 73123ef72..000000000 --- a/cmake/ConfigureChecks.cmake +++ /dev/null @@ -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() diff --git a/src/curses.rs b/src/curses.rs index bd86ff24c..18a47cc63 100644 --- a/src/curses.rs +++ b/src/curses.rs @@ -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> { .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 { - 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(term: Option<&CStr>, fd: i32, configure: F) -> Option> +pub fn setup(term: Option<&CStr>, configure: F) -> Option> 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 { - 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 { + 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 { - 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 { + 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 { - // Take the lock because tparm races with del_curterm, etc. - let _term: std::sync::MutexGuard>> = 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 { - // Take the lock because tparm races with del_curterm, etc. - let _term: std::sync::MutexGuard>> = 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()) } diff --git a/src/env_dispatch.rs b/src/env_dispatch.rs index e859fde17..8697f68f2 100644 --- a/src/env_dispatch.rs +++ b/src/env_dispatch.rs @@ -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.")); diff --git a/src/libc.c b/src/libc.c index 128680e65..93183e5a1 100644 --- a/src/libc.c +++ b/src/libc.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #define UNUSED(x) (void)(x) diff --git a/src/output.rs b/src/output.rs index 544ff3073..0420abecf 100644 --- a/src/output.rs +++ b/src/output.rs @@ -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. diff --git a/src/threads.rs b/src/threads.rs index 8c2d84354..f0c80e01d 100644 --- a/src/threads.rs +++ b/src/threads.rs @@ -214,25 +214,12 @@ pub fn spawn(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 {