Add methods to initialize backends from stdio

This commit is contained in:
Alexandre Bury 2020-08-04 13:54:57 -07:00
parent b4dfaf4d9f
commit bf25cb90ae
4 changed files with 58 additions and 8 deletions

View File

@ -1,11 +1,12 @@
# Changelog
## Next version (cursive-core 0.1.2)
## Next version (cursive-core 0.1.2, cursive 0.15.1)
### API updates
- Add `ProgressBar::set_{min,max,range,counter,label}` for non-chained API.
- Derive Clone, Copy, Debug, PartialEq, Hash for more types.
- Add backend initializers using other files than /dev/tty for ncurses and termion.
### Improvements

View File

@ -193,6 +193,7 @@ impl Backend {
{
enable_raw_mode()?;
// TODO: Use the stdout we define down there
execute!(
io::stdout(),
EnterAlternateScreen,

View File

@ -56,7 +56,24 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> {
impl Backend {
/// Creates a new ncurses-based backend.
///
/// Uses `/dev/tty` for input/output.
pub fn init() -> io::Result<Box<dyn backend::Backend>> {
Self::init_with_files("/dev/tty", "/dev/tty")
}
/// Creates a new ncurses-based backend.
///
/// Uses stdin/stdout for input/output.
pub fn init_stdio() -> io::Result<Box<dyn backend::Backend>> {
Self::init_with_files("/dev/stdin", "/dev/stdout")
}
/// Creates a new ncurses-based backend using the given files for input/output.
pub fn init_with_files(
input_path: &str,
output_path: &str,
) -> io::Result<Box<dyn backend::Backend>> {
// Check the $TERM variable.
if std::env::var("TERM")
.map(|var| var.is_empty())
@ -79,10 +96,17 @@ impl Backend {
// Don't output to standard IO, directly feed into /dev/tty
// This leaves stdin and stdout usable for other purposes.
let tty_path = CString::new("/dev/tty").unwrap();
let mode = CString::new("r+").unwrap();
let tty = unsafe { libc::fopen(tty_path.as_ptr(), mode.as_ptr()) };
ncurses::newterm(None, tty, tty);
let input = {
let mode = CString::new("r").unwrap();
let path = CString::new(input_path).unwrap();
unsafe { libc::fopen(path.as_ptr(), mode.as_ptr()) }
};
let output = {
let mode = CString::new("w").unwrap();
let path = CString::new(output_path).unwrap();
unsafe { libc::fopen(path.as_ptr(), mode.as_ptr()) }
};
ncurses::newterm(None, output, input);
// Enable keypad (like arrows)
ncurses::keypad(ncurses::stdscr(), true);

View File

@ -29,6 +29,7 @@ use std::thread;
/// Backend using termion
pub struct Backend {
// Do we want to make this generic on the writer?
terminal:
RefCell<AlternateScreen<MouseTerminal<RawTerminal<BufWriter<File>>>>>,
current_style: Cell<theme::ColorPair>,
@ -42,12 +43,35 @@ pub struct Backend {
impl Backend {
/// Creates a new termion-based backend.
///
/// Uses `/dev/tty` for input and output.
pub fn init() -> std::io::Result<Box<dyn backend::Backend>> {
Self::init_with_files(
File::open("/dev/tty")?,
File::create("/dev/tty")?,
)
}
/// Creates a new termion-based backend.
///
/// Uses `stdin` and `stdout` for input/output.
pub fn init_stdio() -> std::io::Result<Box<dyn backend::Backend>> {
Self::init_with_files(
File::open("/dev/stdin")?,
File::create("/dev/stdout")?,
)
}
/// Creates a new termion-based backend using the given input and output files.
pub fn init_with_files(
input: File,
output: File,
) -> std::io::Result<Box<dyn backend::Backend>> {
// Use a ~8MB buffer
// Should be enough for a single screen most of the time.
let terminal =
RefCell::new(AlternateScreen::from(MouseTerminal::from(
BufWriter::with_capacity(8_000_000, File::create("/dev/tty")?)
BufWriter::with_capacity(8_000_000, output_file?)
.into_raw_mode()?,
)));
@ -67,7 +91,7 @@ impl Backend {
// We want nonblocking input, but termion is blocking by default
// Read input from a separate thread
thread::spawn(move || {
let input = std::fs::File::open("/dev/tty").unwrap();
let input = input_file.unwrap();
let mut events = input.events();
// Take all the events we can
@ -244,7 +268,7 @@ impl backend::Backend for Backend {
fn screen_size(&self) -> Vec2 {
// TODO: termion::terminal_size currently requires stdout.
// When available, we should try to use /dev/tty instead.
// When available, we should try to use self.terminal or something instead.
let (x, y) = termion::terminal_size().unwrap_or((1, 1));
(x, y).into()
}