mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-02-17 00:22:44 +08:00
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:
parent
2c2ab0c1fa
commit
fc794bab4c
|
@ -25,7 +25,6 @@ endif()
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
add_definitions(-D_UNICODE=1)
|
add_definitions(-D_UNICODE=1)
|
||||||
|
|
||||||
include(cmake/ConfigureChecks.cmake)
|
|
||||||
include(cmake/gettext.cmake)
|
include(cmake/gettext.cmake)
|
||||||
|
|
||||||
# Set up PCRE2
|
# Set up PCRE2
|
||||||
|
|
84
Cargo.lock
generated
84
Cargo.lock
generated
|
@ -117,9 +117,16 @@ dependencies = [
|
||||||
"rand_pcg",
|
"rand_pcg",
|
||||||
"rsconf",
|
"rsconf",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
"terminfo",
|
||||||
"widestring",
|
"widestring",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
|
@ -219,6 +226,18 @@ dependencies = [
|
||||||
"hashbrown",
|
"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]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
|
@ -231,6 +250,16 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
@ -291,6 +320,44 @@ dependencies = [
|
||||||
"pkg-config",
|
"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]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.29"
|
version = "0.3.29"
|
||||||
|
@ -414,6 +481,12 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
@ -442,6 +515,17 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.7"
|
version = "1.1.7"
|
||||||
|
|
|
@ -35,6 +35,7 @@ once_cell = "1.17.0"
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||||
widestring = "1.0.2"
|
widestring = "1.0.2"
|
||||||
git-version = "0.3"
|
git-version = "0.3"
|
||||||
|
terminfo = { git = "https://github.com/meh/rust-terminfo", rev = "870327dd79beaecf50db09a15931d5ee1de2a24d" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand_pcg = "0.3.1"
|
rand_pcg = "0.3.1"
|
||||||
|
|
44
build.rs
44
build.rs
|
@ -3,7 +3,7 @@
|
||||||
use rsconf::{LinkType, Target};
|
use rsconf::{LinkType, Target};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
setup_paths();
|
setup_paths();
|
||||||
|
@ -38,48 +38,6 @@ fn main() {
|
||||||
// Keep verbose mode on until we've ironed out rust build script stuff
|
// Keep verbose mode on until we've ironed out rust build script stuff
|
||||||
target.set_verbose(true);
|
target.set_verbose(true);
|
||||||
detect_cfgs(&mut target);
|
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,
|
/// Check target system support for certain functionality dynamically when the build is invoked,
|
||||||
|
|
|
@ -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()
|
|
470
src/curses.rs
470
src/curses.rs
|
@ -8,7 +8,7 @@
|
||||||
//! This is intentionally very bare bones and only implements the subset of curses functionality
|
//! This is intentionally very bare bones and only implements the subset of curses functionality
|
||||||
//! used by fish
|
//! used by fish
|
||||||
|
|
||||||
use self::sys::*;
|
use crate::common::ToCString;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
@ -36,85 +36,6 @@ pub fn term() -> Option<Arc<Term>> {
|
||||||
.map(Arc::clone)
|
.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()`]
|
/// The safe wrapper around curses functionality, initialized by a successful call to [`setup()`]
|
||||||
/// and obtained thereafter by calls to [`term()`].
|
/// and obtained thereafter by calls to [`term()`].
|
||||||
///
|
///
|
||||||
|
@ -275,157 +196,157 @@ pub struct Term {
|
||||||
impl Term {
|
impl Term {
|
||||||
/// Initialize a new `Term` instance, prepopulating the values of all the curses string
|
/// Initialize a new `Term` instance, prepopulating the values of all the curses string
|
||||||
/// capabilities we care about in the process.
|
/// capabilities we care about in the process.
|
||||||
fn new() -> Self {
|
fn new(db: terminfo::Database) -> Self {
|
||||||
Term {
|
Term {
|
||||||
// String capabilities
|
// String capabilities
|
||||||
enter_bold_mode: get_str_cap("md"),
|
enter_bold_mode: get_str_cap(&db, "md"),
|
||||||
enter_italics_mode: get_str_cap("ZH"),
|
enter_italics_mode: get_str_cap(&db, "ZH"),
|
||||||
exit_italics_mode: get_str_cap("ZR"),
|
exit_italics_mode: get_str_cap(&db, "ZR"),
|
||||||
enter_dim_mode: get_str_cap("mh"),
|
enter_dim_mode: get_str_cap(&db, "mh"),
|
||||||
enter_underline_mode: get_str_cap("us"),
|
enter_underline_mode: get_str_cap(&db, "us"),
|
||||||
exit_underline_mode: get_str_cap("ue"),
|
exit_underline_mode: get_str_cap(&db, "ue"),
|
||||||
enter_reverse_mode: get_str_cap("mr"),
|
enter_reverse_mode: get_str_cap(&db, "mr"),
|
||||||
enter_standout_mode: get_str_cap("so"),
|
enter_standout_mode: get_str_cap(&db, "so"),
|
||||||
exit_standout_mode: get_str_cap("se"),
|
exit_standout_mode: get_str_cap(&db, "se"),
|
||||||
enter_blink_mode: get_str_cap("mb"),
|
enter_blink_mode: get_str_cap(&db, "mb"),
|
||||||
enter_protected_mode: get_str_cap("mp"),
|
enter_protected_mode: get_str_cap(&db, "mp"),
|
||||||
enter_shadow_mode: get_str_cap("ZM"),
|
enter_shadow_mode: get_str_cap(&db, "ZM"),
|
||||||
exit_shadow_mode: get_str_cap("ZU"),
|
exit_shadow_mode: get_str_cap(&db, "ZU"),
|
||||||
enter_secure_mode: get_str_cap("mk"),
|
enter_secure_mode: get_str_cap(&db, "mk"),
|
||||||
enter_alt_charset_mode: get_str_cap("as"),
|
enter_alt_charset_mode: get_str_cap(&db, "as"),
|
||||||
exit_alt_charset_mode: get_str_cap("ae"),
|
exit_alt_charset_mode: get_str_cap(&db, "ae"),
|
||||||
set_a_foreground: get_str_cap("AF"),
|
set_a_foreground: get_str_cap(&db, "AF"),
|
||||||
set_foreground: get_str_cap("Sf"),
|
set_foreground: get_str_cap(&db, "Sf"),
|
||||||
set_a_background: get_str_cap("AB"),
|
set_a_background: get_str_cap(&db, "AB"),
|
||||||
set_background: get_str_cap("Sb"),
|
set_background: get_str_cap(&db, "Sb"),
|
||||||
exit_attribute_mode: get_str_cap("me"),
|
exit_attribute_mode: get_str_cap(&db, "me"),
|
||||||
set_title: get_str_cap("ts"),
|
set_title: get_str_cap(&db, "ts"),
|
||||||
clear_screen: get_str_cap("cl"),
|
clear_screen: get_str_cap(&db, "cl"),
|
||||||
cursor_up: get_str_cap("up"),
|
cursor_up: get_str_cap(&db, "up"),
|
||||||
cursor_down: get_str_cap("do"),
|
cursor_down: get_str_cap(&db, "do"),
|
||||||
cursor_left: get_str_cap("le"),
|
cursor_left: get_str_cap(&db, "le"),
|
||||||
cursor_right: get_str_cap("nd"),
|
cursor_right: get_str_cap(&db, "nd"),
|
||||||
parm_left_cursor: get_str_cap("LE"),
|
parm_left_cursor: get_str_cap(&db, "LE"),
|
||||||
parm_right_cursor: get_str_cap("RI"),
|
parm_right_cursor: get_str_cap(&db, "RI"),
|
||||||
clr_eol: get_str_cap("ce"),
|
clr_eol: get_str_cap(&db, "ce"),
|
||||||
clr_eos: get_str_cap("cd"),
|
clr_eos: get_str_cap(&db, "cd"),
|
||||||
|
|
||||||
// Number capabilities
|
// Number capabilities
|
||||||
max_colors: get_num_cap("Co"),
|
max_colors: get_num_cap(&db, "Co"),
|
||||||
init_tabs: get_num_cap("it"),
|
init_tabs: get_num_cap(&db, "it"),
|
||||||
|
|
||||||
// Flag/boolean capabilities
|
// Flag/boolean capabilities
|
||||||
eat_newline_glitch: get_flag_cap("xn"),
|
eat_newline_glitch: get_flag_cap(&db, "xn"),
|
||||||
auto_right_margin: get_flag_cap("am"),
|
auto_right_margin: get_flag_cap(&db, "am"),
|
||||||
|
|
||||||
// Keys. See `man terminfo` for these strings.
|
// Keys. See `man terminfo` for these strings.
|
||||||
key_a1: get_str_cap("K1"),
|
key_a1: get_str_cap(&db, "K1"),
|
||||||
key_a3: get_str_cap("K3"),
|
key_a3: get_str_cap(&db, "K3"),
|
||||||
key_b2: get_str_cap("K2"),
|
key_b2: get_str_cap(&db, "K2"),
|
||||||
key_backspace: get_str_cap("kb"),
|
key_backspace: get_str_cap(&db, "kb"),
|
||||||
key_beg: get_str_cap("@1"),
|
key_beg: get_str_cap(&db, "@1"),
|
||||||
key_btab: get_str_cap("kB"),
|
key_btab: get_str_cap(&db, "kB"),
|
||||||
key_c1: get_str_cap("K4"),
|
key_c1: get_str_cap(&db, "K4"),
|
||||||
key_c3: get_str_cap("K5"),
|
key_c3: get_str_cap(&db, "K5"),
|
||||||
key_cancel: get_str_cap("@2"),
|
key_cancel: get_str_cap(&db, "@2"),
|
||||||
key_catab: get_str_cap("ka"),
|
key_catab: get_str_cap(&db, "ka"),
|
||||||
key_clear: get_str_cap("kC"),
|
key_clear: get_str_cap(&db, "kC"),
|
||||||
key_close: get_str_cap("@3"),
|
key_close: get_str_cap(&db, "@3"),
|
||||||
key_command: get_str_cap("@4"),
|
key_command: get_str_cap(&db, "@4"),
|
||||||
key_copy: get_str_cap("@5"),
|
key_copy: get_str_cap(&db, "@5"),
|
||||||
key_create: get_str_cap("@6"),
|
key_create: get_str_cap(&db, "@6"),
|
||||||
key_ctab: get_str_cap("kt"),
|
key_ctab: get_str_cap(&db, "kt"),
|
||||||
key_dc: get_str_cap("kD"),
|
key_dc: get_str_cap(&db, "kD"),
|
||||||
key_dl: get_str_cap("kL"),
|
key_dl: get_str_cap(&db, "kL"),
|
||||||
key_down: get_str_cap("kd"),
|
key_down: get_str_cap(&db, "kd"),
|
||||||
key_eic: get_str_cap("kM"),
|
key_eic: get_str_cap(&db, "kM"),
|
||||||
key_end: get_str_cap("@7"),
|
key_end: get_str_cap(&db, "@7"),
|
||||||
key_enter: get_str_cap("@8"),
|
key_enter: get_str_cap(&db, "@8"),
|
||||||
key_eol: get_str_cap("kE"),
|
key_eol: get_str_cap(&db, "kE"),
|
||||||
key_eos: get_str_cap("kS"),
|
key_eos: get_str_cap(&db, "kS"),
|
||||||
key_exit: get_str_cap("@9"),
|
key_exit: get_str_cap(&db, "@9"),
|
||||||
key_f0: get_str_cap("k0"),
|
key_f0: get_str_cap(&db, "k0"),
|
||||||
key_f1: get_str_cap("k1"),
|
key_f1: get_str_cap(&db, "k1"),
|
||||||
key_f10: get_str_cap("k;"),
|
key_f10: get_str_cap(&db, "k;"),
|
||||||
key_f11: get_str_cap("F1"),
|
key_f11: get_str_cap(&db, "F1"),
|
||||||
key_f12: get_str_cap("F2"),
|
key_f12: get_str_cap(&db, "F2"),
|
||||||
key_f13: get_str_cap("F3"),
|
key_f13: get_str_cap(&db, "F3"),
|
||||||
key_f14: get_str_cap("F4"),
|
key_f14: get_str_cap(&db, "F4"),
|
||||||
key_f15: get_str_cap("F5"),
|
key_f15: get_str_cap(&db, "F5"),
|
||||||
key_f16: get_str_cap("F6"),
|
key_f16: get_str_cap(&db, "F6"),
|
||||||
key_f17: get_str_cap("F7"),
|
key_f17: get_str_cap(&db, "F7"),
|
||||||
key_f18: get_str_cap("F8"),
|
key_f18: get_str_cap(&db, "F8"),
|
||||||
key_f19: get_str_cap("F9"),
|
key_f19: get_str_cap(&db, "F9"),
|
||||||
key_f2: get_str_cap("k2"),
|
key_f2: get_str_cap(&db, "k2"),
|
||||||
key_f20: get_str_cap("FA"),
|
key_f20: get_str_cap(&db, "FA"),
|
||||||
// Note key_f21 through key_f63 are available but no actual keyboard supports them.
|
// Note key_f21 through key_f63 are available but no actual keyboard supports them.
|
||||||
key_f3: get_str_cap("k3"),
|
key_f3: get_str_cap(&db, "k3"),
|
||||||
key_f4: get_str_cap("k4"),
|
key_f4: get_str_cap(&db, "k4"),
|
||||||
key_f5: get_str_cap("k5"),
|
key_f5: get_str_cap(&db, "k5"),
|
||||||
key_f6: get_str_cap("k6"),
|
key_f6: get_str_cap(&db, "k6"),
|
||||||
key_f7: get_str_cap("k7"),
|
key_f7: get_str_cap(&db, "k7"),
|
||||||
key_f8: get_str_cap("k8"),
|
key_f8: get_str_cap(&db, "k8"),
|
||||||
key_f9: get_str_cap("k9"),
|
key_f9: get_str_cap(&db, "k9"),
|
||||||
key_find: get_str_cap("@0"),
|
key_find: get_str_cap(&db, "@0"),
|
||||||
key_help: get_str_cap("%1"),
|
key_help: get_str_cap(&db, "%1"),
|
||||||
key_home: get_str_cap("kh"),
|
key_home: get_str_cap(&db, "kh"),
|
||||||
key_ic: get_str_cap("kI"),
|
key_ic: get_str_cap(&db, "kI"),
|
||||||
key_il: get_str_cap("kA"),
|
key_il: get_str_cap(&db, "kA"),
|
||||||
key_left: get_str_cap("kl"),
|
key_left: get_str_cap(&db, "kl"),
|
||||||
key_ll: get_str_cap("kH"),
|
key_ll: get_str_cap(&db, "kH"),
|
||||||
key_mark: get_str_cap("%2"),
|
key_mark: get_str_cap(&db, "%2"),
|
||||||
key_message: get_str_cap("%3"),
|
key_message: get_str_cap(&db, "%3"),
|
||||||
key_move: get_str_cap("%4"),
|
key_move: get_str_cap(&db, "%4"),
|
||||||
key_next: get_str_cap("%5"),
|
key_next: get_str_cap(&db, "%5"),
|
||||||
key_npage: get_str_cap("kN"),
|
key_npage: get_str_cap(&db, "kN"),
|
||||||
key_open: get_str_cap("%6"),
|
key_open: get_str_cap(&db, "%6"),
|
||||||
key_options: get_str_cap("%7"),
|
key_options: get_str_cap(&db, "%7"),
|
||||||
key_ppage: get_str_cap("kP"),
|
key_ppage: get_str_cap(&db, "kP"),
|
||||||
key_previous: get_str_cap("%8"),
|
key_previous: get_str_cap(&db, "%8"),
|
||||||
key_print: get_str_cap("%9"),
|
key_print: get_str_cap(&db, "%9"),
|
||||||
key_redo: get_str_cap("%0"),
|
key_redo: get_str_cap(&db, "%0"),
|
||||||
key_reference: get_str_cap("&1"),
|
key_reference: get_str_cap(&db, "&1"),
|
||||||
key_refresh: get_str_cap("&2"),
|
key_refresh: get_str_cap(&db, "&2"),
|
||||||
key_replace: get_str_cap("&3"),
|
key_replace: get_str_cap(&db, "&3"),
|
||||||
key_restart: get_str_cap("&4"),
|
key_restart: get_str_cap(&db, "&4"),
|
||||||
key_resume: get_str_cap("&5"),
|
key_resume: get_str_cap(&db, "&5"),
|
||||||
key_right: get_str_cap("kr"),
|
key_right: get_str_cap(&db, "kr"),
|
||||||
key_save: get_str_cap("&6"),
|
key_save: get_str_cap(&db, "&6"),
|
||||||
key_sbeg: get_str_cap("&9"),
|
key_sbeg: get_str_cap(&db, "&9"),
|
||||||
key_scancel: get_str_cap("&0"),
|
key_scancel: get_str_cap(&db, "&0"),
|
||||||
key_scommand: get_str_cap("*1"),
|
key_scommand: get_str_cap(&db, "*1"),
|
||||||
key_scopy: get_str_cap("*2"),
|
key_scopy: get_str_cap(&db, "*2"),
|
||||||
key_screate: get_str_cap("*3"),
|
key_screate: get_str_cap(&db, "*3"),
|
||||||
key_sdc: get_str_cap("*4"),
|
key_sdc: get_str_cap(&db, "*4"),
|
||||||
key_sdl: get_str_cap("*5"),
|
key_sdl: get_str_cap(&db, "*5"),
|
||||||
key_select: get_str_cap("*6"),
|
key_select: get_str_cap(&db, "*6"),
|
||||||
key_send: get_str_cap("*7"),
|
key_send: get_str_cap(&db, "*7"),
|
||||||
key_seol: get_str_cap("*8"),
|
key_seol: get_str_cap(&db, "*8"),
|
||||||
key_sexit: get_str_cap("*9"),
|
key_sexit: get_str_cap(&db, "*9"),
|
||||||
key_sf: get_str_cap("kF"),
|
key_sf: get_str_cap(&db, "kF"),
|
||||||
key_sfind: get_str_cap("*0"),
|
key_sfind: get_str_cap(&db, "*0"),
|
||||||
key_shelp: get_str_cap("#1"),
|
key_shelp: get_str_cap(&db, "#1"),
|
||||||
key_shome: get_str_cap("#2"),
|
key_shome: get_str_cap(&db, "#2"),
|
||||||
key_sic: get_str_cap("#3"),
|
key_sic: get_str_cap(&db, "#3"),
|
||||||
key_sleft: get_str_cap("#4"),
|
key_sleft: get_str_cap(&db, "#4"),
|
||||||
key_smessage: get_str_cap("%a"),
|
key_smessage: get_str_cap(&db, "%a"),
|
||||||
key_smove: get_str_cap("%b"),
|
key_smove: get_str_cap(&db, "%b"),
|
||||||
key_snext: get_str_cap("%c"),
|
key_snext: get_str_cap(&db, "%c"),
|
||||||
key_soptions: get_str_cap("%d"),
|
key_soptions: get_str_cap(&db, "%d"),
|
||||||
key_sprevious: get_str_cap("%e"),
|
key_sprevious: get_str_cap(&db, "%e"),
|
||||||
key_sprint: get_str_cap("%f"),
|
key_sprint: get_str_cap(&db, "%f"),
|
||||||
key_sr: get_str_cap("kR"),
|
key_sr: get_str_cap(&db, "kR"),
|
||||||
key_sredo: get_str_cap("%g"),
|
key_sredo: get_str_cap(&db, "%g"),
|
||||||
key_sreplace: get_str_cap("%h"),
|
key_sreplace: get_str_cap(&db, "%h"),
|
||||||
key_sright: get_str_cap("%i"),
|
key_sright: get_str_cap(&db, "%i"),
|
||||||
key_srsume: get_str_cap("%j"),
|
key_srsume: get_str_cap(&db, "%j"),
|
||||||
key_ssave: get_str_cap("!1"),
|
key_ssave: get_str_cap(&db, "!1"),
|
||||||
key_ssuspend: get_str_cap("!2"),
|
key_ssuspend: get_str_cap(&db, "!2"),
|
||||||
key_stab: get_str_cap("kT"),
|
key_stab: get_str_cap(&db, "kT"),
|
||||||
key_sundo: get_str_cap("!3"),
|
key_sundo: get_str_cap(&db, "!3"),
|
||||||
key_suspend: get_str_cap("&7"),
|
key_suspend: get_str_cap(&db, "&7"),
|
||||||
key_undo: get_str_cap("&8"),
|
key_undo: get_str_cap(&db, "&8"),
|
||||||
key_up: get_str_cap("ku"),
|
key_up: get_str_cap(&db, "ku"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,7 +362,7 @@ impl Term {
|
||||||
/// error output to stderr in case of failure.
|
/// error output to stderr in case of failure.
|
||||||
///
|
///
|
||||||
/// Any existing references from `curses::term()` will be invalidated by this call!
|
/// 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
|
where
|
||||||
F: Fn(&mut Term),
|
F: Fn(&mut Term),
|
||||||
{
|
{
|
||||||
|
@ -449,25 +370,19 @@ where
|
||||||
// curses itself. We might split this to another lock in the future.
|
// curses itself. We might split this to another lock in the future.
|
||||||
let mut global_term = TERM.lock().expect("Mutex poisoned!");
|
let mut global_term = TERM.lock().expect("Mutex poisoned!");
|
||||||
|
|
||||||
let result = unsafe {
|
let res = if let Some(term) = term {
|
||||||
// If cur_term is already initialized for a different $TERM value, calling setupterm() again
|
terminfo::Database::from_name(term.to_str().unwrap())
|
||||||
// will leak memory. Call del_curterm() first to free previously allocated resources.
|
} else {
|
||||||
let _ = sys::del_curterm(get_curterm());
|
// For historical reasons getting "None" means to get it from the environment.
|
||||||
|
terminfo::Database::from_env()
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safely store the new Term instance or replace the old one. We have the lock so it's safe to
|
// 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.
|
// 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
|
// Create a new `Term` instance, prepopulate the capabilities we care about, and allow the
|
||||||
// caller to override any as needed.
|
// caller to override any as needed.
|
||||||
let mut term = Term::new();
|
let mut term = Term::new(result);
|
||||||
(configure)(&mut term);
|
(configure)(&mut term);
|
||||||
|
|
||||||
let term = Arc::new(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.
|
/// 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.
|
/// Panics if the given code string does not contain exactly two bytes.
|
||||||
fn get_str_cap(code: &str) -> Option<CString> {
|
fn get_str_cap(db: &terminfo::Database, code: &str) -> Option<CString> {
|
||||||
let code = to_cstr_code(code);
|
db.raw(code).map(|cap| match cap {
|
||||||
// termcap spec says nul is not allowed in terminal sequences and must be encoded;
|
terminfo::Value::True => "1".to_string().as_bytes().to_cstring(),
|
||||||
// so the terminating NUL is the end of the string.
|
terminfo::Value::Number(n) => n.to_string().as_bytes().to_cstring(),
|
||||||
let tstr = unsafe { sys::tgetstr(code.as_ptr(), core::ptr::null_mut()) };
|
terminfo::Value::String(s) => s.clone().to_cstring(),
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a number capability from termcap, or None if missing.
|
/// Return a number capability from termcap, or None if missing.
|
||||||
/// Panics if the given code string does not contain exactly two bytes.
|
/// Panics if the given code string does not contain exactly two bytes.
|
||||||
fn get_num_cap(code: &str) -> Option<usize> {
|
fn get_num_cap(db: &terminfo::Database, code: &str) -> Option<usize> {
|
||||||
let code = to_cstr_code(code);
|
match db.raw(code) {
|
||||||
match unsafe { sys::tgetnum(code.as_ptr()) } {
|
Some(terminfo::Value::Number(n)) if *n >= 0 => Some(*n as usize),
|
||||||
-1 => None,
|
_ => None,
|
||||||
n => Some(usize::try_from(n).unwrap()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a flag capability from termcap, or false if missing.
|
/// Return a flag capability from termcap, or false if missing.
|
||||||
/// Panics if the given code string does not contain exactly two bytes.
|
/// Panics if the given code string does not contain exactly two bytes.
|
||||||
fn get_flag_cap(code: &str) -> bool {
|
fn get_flag_cap(db: &terminfo::Database, code: &str) -> bool {
|
||||||
let code = to_cstr_code(code);
|
db.raw(code)
|
||||||
unsafe { sys::tgetflag(code.as_ptr()) != 0 }
|
.map(|cap| matches!(cap, terminfo::Value::True))
|
||||||
}
|
.unwrap_or(false)
|
||||||
|
|
||||||
/// `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]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Covers over tparm().
|
/// Covers over tparm().
|
||||||
pub fn tparm0(cap: &CStr) -> Option<CString> {
|
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());
|
assert!(!cap.to_bytes().is_empty());
|
||||||
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
|
let cap = cap.to_bytes();
|
||||||
// Safety: we're trusting tparm here.
|
terminfo::expand!(cap).ok().map(|x| x.to_cstring())
|
||||||
unsafe { try_ptr_to_cstr(tparm(cap_ptr)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Covers over tparm().
|
/// Covers over tparm().
|
||||||
pub fn tparm1(cap: &CStr, param1: i32) -> Option<CString> {
|
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());
|
assert!(!cap.to_bytes().is_empty());
|
||||||
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
|
let cap = cap.to_bytes();
|
||||||
// Safety: we're trusting tparm here.
|
terminfo::expand!(cap; param1).ok().map(|x| x.to_cstring())
|
||||||
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) }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// `term` here is one of our hard-coded strings above; we can unwrap because we can
|
||||||
// guarantee it doesn't contain any interior NULs.
|
// guarantee it doesn't contain any interior NULs.
|
||||||
let term_cstr = CString::new(term).unwrap();
|
let term_cstr = CString::new(term).unwrap();
|
||||||
let success = curses::setup(Some(&term_cstr), libc::STDOUT_FILENO, |term| {
|
let success =
|
||||||
apply_term_hacks(vars, term)
|
curses::setup(Some(&term_cstr), |term| apply_term_hacks(vars, term)).is_some();
|
||||||
})
|
|
||||||
.is_some();
|
|
||||||
if is_interactive_session() {
|
if is_interactive_session() {
|
||||||
if success {
|
if success {
|
||||||
FLOG!(warning, wgettext!("Using fallback terminal type"), term);
|
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| {
|
if curses::setup(None, |term| apply_term_hacks(vars, term)).is_none() {
|
||||||
apply_term_hacks(vars, term)
|
|
||||||
})
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
if is_interactive_session() {
|
if is_interactive_session() {
|
||||||
let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string());
|
let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string());
|
||||||
FLOG!(warning, wgettext!("Could not set up terminal."));
|
FLOG!(warning, wgettext!("Could not set up terminal."));
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <term.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define UNUSED(x) (void)(x)
|
#define UNUSED(x) (void)(x)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Generic output functions.
|
// Generic output functions.
|
||||||
use crate::color::RgbColor;
|
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::curses::{self, tparm1, Term};
|
||||||
use crate::env::EnvVar;
|
use crate::env::EnvVar;
|
||||||
use crate::wchar::prelude::*;
|
use crate::wchar::prelude::*;
|
||||||
|
@ -10,7 +10,6 @@ use std::ffi::CStr;
|
||||||
use std::io::{Result, Write};
|
use std::io::{Result, Write};
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Copy, Clone, Default)]
|
#[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 {
|
impl Outputter {
|
||||||
/// Emit a terminfo string, like tputs.
|
/// Emit a terminfo string, like tputs.
|
||||||
/// affcnt (number of lines affected) is assumed to be 1, i.e. not applicable.
|
/// affcnt (number of lines affected) is assumed to be 1, i.e. not applicable.
|
||||||
pub fn tputs(&mut self, str: &CStr) {
|
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();
|
self.begin_buffering();
|
||||||
let _ = curses::tputs(str, affcnt, tputs_writer);
|
let _ = self.write(str.to_bytes());
|
||||||
self.end_buffering();
|
self.end_buffering();
|
||||||
unsafe { TPUTS_RECEIVER = saved_recv };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience cover over tputs, in recognition of the fact that our Term has Optional fields.
|
/// Convenience cover over tputs, in recognition of the fact that our Term has Optional fields.
|
||||||
|
|
|
@ -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.
|
/// 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) {
|
pub fn asan_maybe_exit(code: i32) {
|
||||||
if cfg!(feature = "asan") {
|
if cfg!(feature = "asan") {
|
||||||
asan_before_exit();
|
|
||||||
unsafe {
|
unsafe {
|
||||||
libc::exit(code);
|
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`].
|
/// Data shared between the thread pool [`ThreadPool`] and worker threads [`WorkerThread`].
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ThreadPoolProtected {
|
struct ThreadPoolProtected {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user