Adding upstream version 0.2.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
c7e0ec57a4
commit
7f48381065
14 changed files with 647 additions and 0 deletions
6
.cargo_vcs_info.json
Normal file
6
.cargo_vcs_info.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"git": {
|
||||
"sha1": "0fbe095a9b44a8da5b42c972b54af660a340acda"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
20
CHANGELOG
Normal file
20
CHANGELOG
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Version 0.2.3 - 2023-08-09
|
||||
- [change][patch] Remove use of `IsTerminal` to support rust version 1.66 and up.
|
||||
- [change][patch] Declare minimum rust version in `Cargo.toml`.
|
||||
|
||||
# Version 0.2.2 - 2023-08-08
|
||||
- [fix][minor] Fix printing of newline in `Terminal::read_input_line()`.
|
||||
|
||||
# Version 0.2.1 - 2023-08-04
|
||||
- [change][patch] Fix category slug in `Cargo.toml`.
|
||||
|
||||
# Version 0.2.0 - 2023-08-04
|
||||
- [rename][major] Rename `TerminalPrompter` to `Terminal`.
|
||||
- [rename][major] Rename `read_line()` to `read_input_line()`.
|
||||
- [add][minor] Implement `BufRead` for `Terminal`.
|
||||
- [change][minor] Try `sterr`, `stdin`, `stdout` and `/dev/tty` in that order on Unix.
|
||||
- [change][minor] Do not cache the terminal mode, retrieve it every time when needed.
|
||||
- [add][minor] Add documentation.
|
||||
|
||||
# Version 0.1.0 - 2023-08-03
|
||||
- [add][minor] Initial release.
|
39
Cargo.lock
generated
Normal file
39
Cargo.lock
generated
Normal file
|
@ -0,0 +1,39 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "terminal-prompt"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
41
Cargo.toml
Normal file
41
Cargo.toml
Normal file
|
@ -0,0 +1,41 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
name = "terminal-prompt"
|
||||
version = "0.2.3"
|
||||
authors = ["Maarten de Vries <maarten@de-vri.es>"]
|
||||
publish = ["crates-io"]
|
||||
description = "Tiny library for prompting sensitive or non-sensitive data on the terminal"
|
||||
documentation = "https://docs.rs/terminal-prompt"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"terminal",
|
||||
"console",
|
||||
"prompt",
|
||||
"input",
|
||||
"tty",
|
||||
]
|
||||
categories = [
|
||||
"command-line-interface",
|
||||
"os",
|
||||
]
|
||||
license = "BSD-2-Clause"
|
||||
repository = "https://github.com/de-vri-es/terminal-prompt-rs"
|
||||
|
||||
[target."cfg(unix)".dependencies.libc]
|
||||
version = "0.2.147"
|
||||
|
||||
[target."cfg(windows)".dependencies.winapi]
|
||||
version = "0.3.9"
|
||||
features = ["consoleapi"]
|
22
Cargo.toml.orig
generated
Normal file
22
Cargo.toml.orig
generated
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "terminal-prompt"
|
||||
version = "0.2.3"
|
||||
description = "Tiny library for prompting sensitive or non-sensitive data on the terminal"
|
||||
license = "BSD-2-Clause"
|
||||
|
||||
authors = ["Maarten de Vries <maarten@de-vri.es>"]
|
||||
repository = "https://github.com/de-vri-es/terminal-prompt-rs"
|
||||
documentation = "https://docs.rs/terminal-prompt"
|
||||
|
||||
keywords = ["terminal", "console", "prompt", "input", "tty"]
|
||||
categories = ["command-line-interface", "os"]
|
||||
|
||||
publish = ["crates-io"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.147"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["consoleapi"] }
|
24
LICENSE.md
Normal file
24
LICENSE.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2023, Maarten de Vries <maarten@de-vri.es>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# terminal-prompt
|
||||
|
||||
Tiny library for prompting sensitive or non-sensitive data on the terminal.
|
||||
|
||||
The only dependency is `libc` on Unix and `winapi` on Windows.
|
||||
|
||||
See [`Terminal`] for the API documentation.
|
||||
|
||||
## Example
|
||||
Read a username and password from the terminal:
|
||||
```rust
|
||||
use terminal_prompt::Terminal;
|
||||
let mut terminal = Terminal::open()?;
|
||||
let username = terminal.prompt("Username: ")?;
|
||||
let password = terminal.prompt_sensitive("Password: ")?;
|
||||
```
|
||||
|
||||
[`Terminal`]: https://docs.rs/terminal-prompt/latest/terminal_prompt/struct.Terminal.html
|
5
README.tpl
Normal file
5
README.tpl
Normal file
|
@ -0,0 +1,5 @@
|
|||
# {{crate}}
|
||||
|
||||
{{readme}}
|
||||
|
||||
[`Terminal`]: https://docs.rs/terminal-prompt/latest/terminal_prompt/struct.Terminal.html
|
10
examples/username-password.rs
Normal file
10
examples/username-password.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use terminal_prompt::Terminal;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let mut terminal = Terminal::open()?;
|
||||
let username = terminal.prompt("Username: ")?;
|
||||
let password = terminal.prompt_sensitive("Password: ")?;
|
||||
println!("Username: {username}");
|
||||
println!("Password: {password}");
|
||||
Ok(())
|
||||
}
|
191
src/lib.rs
Normal file
191
src/lib.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
//! Tiny library for prompting sensitive or non-sensitive data on the terminal.
|
||||
//!
|
||||
//! The only dependency is `libc` on Unix and `winapi` on Windows.
|
||||
//!
|
||||
//! See [`Terminal`] for the API documentation.
|
||||
//!
|
||||
//! # Example
|
||||
//! Read a username and password from the terminal:
|
||||
//! ```no_run
|
||||
//! # fn main() -> std::io::Result<()> {
|
||||
//! use terminal_prompt::Terminal;
|
||||
//! let mut terminal = Terminal::open()?;
|
||||
//! let username = terminal.prompt("Username: ")?;
|
||||
//! let password = terminal.prompt_sensitive("Password: ")?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::io::{BufReader, BufRead, Read, Write};
|
||||
|
||||
mod sys;
|
||||
|
||||
/// A handle to the terminal associated with the current process.
|
||||
///
|
||||
/// Once opened, you can use [`Self::prompt()`] to read non-sensitive data from the terminal,
|
||||
/// and [`Self::prompt_sensitive()`] to read sensitive data like passwords.
|
||||
///
|
||||
/// Alternatively, you can manually call [`Self::enable_echo()`] and [`Self::disable_echo()`], and read/write from the terminal directly.
|
||||
/// The terminal handle implements the standard [`Read`], [`Write`] and [`BufRead`] traits,
|
||||
/// and it has a [`Self::read_line()`] convenience function that returns a new string.
|
||||
///
|
||||
/// # Terminal modes
|
||||
/// When opened, the terminal will be put in line editing mode.
|
||||
/// When dropped, the original mode of the terminal will be restored.
|
||||
/// Note that the terminal is inherently a global resource,
|
||||
/// so creating multiple terminal objects and dropping them in a different order can cause the terminal to be left in a different mode.
|
||||
pub struct Terminal {
|
||||
/// The underlying terminal.
|
||||
terminal: BufReader<sys::Terminal>,
|
||||
|
||||
/// The mode of the terminal when we opened it.
|
||||
initial_mode: sys::TerminalMode,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
/// Open the terminal associated with the current process.
|
||||
///
|
||||
/// The exact behavior is platform dependent.
|
||||
///
|
||||
/// On Unix platforms, if one of standard I/O streams is a terminal, that terminal is used.
|
||||
/// First standard error is tried, then standard input and finally standard output.
|
||||
/// If none of those work, the function tries to open `/dev/tty`.
|
||||
/// This means that on Unix platforms, the terminal prompt can still work, even when both standard input and standard output are connected to pipes instead of the terminal.
|
||||
///
|
||||
/// On Windows, if both standard input and standard error are connected to a terminal, those streams are used.
|
||||
///
|
||||
/// In all cases, if the function fails to find a terminal for the process, an error is returned.
|
||||
pub fn open() -> std::io::Result<Self> {
|
||||
// Open the terminal and retrieve the initial mode.
|
||||
let terminal = sys::Terminal::open()?;
|
||||
let initial_mode = terminal.get_terminal_mode()?;
|
||||
|
||||
// Enable line editing mode.
|
||||
let mut mode = initial_mode;
|
||||
mode.enable_line_editing();
|
||||
terminal.set_terminal_mode(&mode)?;
|
||||
|
||||
Ok(Self {
|
||||
terminal: BufReader::new(terminal),
|
||||
initial_mode,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the terminal is echoing input.
|
||||
///
|
||||
/// If enabled, any text typed on the terminal will be visible.
|
||||
pub fn is_echo_enabled(&self) -> std::io::Result<bool> {
|
||||
let mode = self.terminal.get_ref().get_terminal_mode()?;
|
||||
Ok(mode.is_echo_enabled())
|
||||
}
|
||||
|
||||
/// Disable echoing of terminal input.
|
||||
///
|
||||
/// This will prevent text typed on the terminal from being visible.
|
||||
/// This can be used to hide passwords while they are being typed.
|
||||
pub fn disable_echo(&self) -> std::io::Result<()> {
|
||||
let mut mode = self.terminal.get_ref().get_terminal_mode()?;
|
||||
mode.disable_echo();
|
||||
self.terminal.get_ref().set_terminal_mode(&mode)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable echoing of terminal input.
|
||||
///
|
||||
/// This will cause any text typed on the terminal to be visible.
|
||||
pub fn enable_echo(&mut self) -> std::io::Result<()> {
|
||||
let mut mode = self.terminal.get_ref().get_terminal_mode()?;
|
||||
mode.enable_echo();
|
||||
self.terminal.get_ref().set_terminal_mode(&mode)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a line of input from the terminal.
|
||||
///
|
||||
/// If echoing is disabled, this will also print a newline character to visually indicate to the user.
|
||||
/// If this is not desired, use the [`BufRead::read_line()`] function instead.
|
||||
pub fn read_input_line(&mut self) -> std::io::Result<String> {
|
||||
let mut buffer = String::new();
|
||||
self.terminal.read_line(&mut buffer)?;
|
||||
|
||||
if self.is_echo_enabled().ok() == Some(false) {
|
||||
writeln!(self).ok();
|
||||
}
|
||||
if buffer.ends_with('\n') {
|
||||
buffer.pop();
|
||||
}
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Prompt the user on the terminal.
|
||||
///
|
||||
/// This function does not enable or disable echoing and should not normally be used for reading sensitive data like passwords.
|
||||
/// Consider [`Self::prompt_sensitive()`] instead.
|
||||
pub fn prompt(&mut self, prompt: impl std::fmt::Display) -> std::io::Result<String> {
|
||||
write!(self, "{prompt}")?;
|
||||
self.read_input_line()
|
||||
}
|
||||
|
||||
/// Prompt the user for sensitive data (like passwords) on the terminal.
|
||||
///
|
||||
/// This function makes sure that echoing is disabled before the prompt is shown.
|
||||
/// If echoing was enabled, it is re-enabled after the response is read.
|
||||
///
|
||||
/// Use [`Self::prompt()`] to read non-sensitive data.
|
||||
pub fn prompt_sensitive(&mut self, prompt: impl std::fmt::Display) -> std::io::Result<String> {
|
||||
let old_mode = self.terminal.get_ref().get_terminal_mode()?;
|
||||
if old_mode.is_echo_enabled() {
|
||||
let mut new_mode = old_mode;
|
||||
new_mode.disable_echo();
|
||||
self.terminal.get_ref().set_terminal_mode(&new_mode)?;
|
||||
}
|
||||
write!(self, "{prompt}")?;
|
||||
let line = self.read_input_line();
|
||||
if old_mode.is_echo_enabled() {
|
||||
self.terminal.get_ref().set_terminal_mode(&old_mode).ok();
|
||||
}
|
||||
line
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Terminal {
|
||||
fn drop(&mut self) {
|
||||
self.terminal.get_ref().set_terminal_mode(&self.initial_mode).ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Terminal {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.terminal.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
|
||||
self.terminal.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufRead for Terminal {
|
||||
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
|
||||
self.terminal.fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.terminal.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Terminal {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.terminal.get_mut().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.terminal.get_mut().flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
|
||||
self.terminal.get_mut().write_vectored(bufs)
|
||||
}
|
||||
}
|
11
src/sys/mod.rs
Normal file
11
src/sys/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#[cfg(unix)]
|
||||
mod unix;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use unix::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use windows::*;
|
142
src/sys/unix.rs
Normal file
142
src/sys/unix.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
|
||||
|
||||
/// Unix handle to an open terminal.
|
||||
pub enum Terminal {
|
||||
/// Non-owning file for one of the standard I/O streams.
|
||||
Stdio(ManuallyDrop<File>),
|
||||
|
||||
/// Owned file for `/dev/tty`.
|
||||
File(File),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TerminalMode {
|
||||
termios: libc::termios,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn open() -> std::io::Result<Self> {
|
||||
if let Some(terminal) = open_fd_terminal(2) {
|
||||
Ok(terminal)
|
||||
} else if let Some(terminal) = open_fd_terminal(0) {
|
||||
Ok(terminal)
|
||||
} else if let Some(terminal) = open_fd_terminal(1) {
|
||||
Ok(terminal)
|
||||
} else {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/tty")?;
|
||||
if is_terminal(file.as_fd()) {
|
||||
Ok(Self::File(file))
|
||||
} else {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOTTY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_terminal_mode(&self) -> std::io::Result<TerminalMode> {
|
||||
unsafe {
|
||||
let mut termios = std::mem::zeroed();
|
||||
check_ret(libc::tcgetattr(self.as_fd().as_raw_fd(), &mut termios))?;
|
||||
Ok(TerminalMode { termios })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_terminal_mode(&self, mode: &TerminalMode) -> std::io::Result<()> {
|
||||
unsafe {
|
||||
check_ret(libc::tcsetattr(
|
||||
self.as_fd().as_raw_fd(),
|
||||
libc::TCSANOW,
|
||||
&mode.termios,
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn as_file(&self) -> &File {
|
||||
match self {
|
||||
Self::Stdio(io) => io,
|
||||
Self::File(io) => io,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_fd_terminal(fd: RawFd) -> Option<Terminal> {
|
||||
let file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
|
||||
if is_terminal(file.as_fd()) {
|
||||
Some(Terminal::Stdio(file))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalMode {
|
||||
pub fn enable_line_editing(&mut self) {
|
||||
self.termios.c_lflag |= libc::ICANON;
|
||||
}
|
||||
|
||||
pub fn disable_echo(&mut self) {
|
||||
self.termios.c_lflag &= !libc::ECHO;
|
||||
self.termios.c_lflag &= !libc::ICANON;
|
||||
}
|
||||
|
||||
pub fn enable_echo(&mut self) {
|
||||
self.termios.c_lflag |= libc::ECHO;
|
||||
self.termios.c_lflag |= !libc::ICANON;
|
||||
}
|
||||
|
||||
pub fn is_echo_enabled(&self) -> bool {
|
||||
self.termios.c_lflag & libc::ECHO != 0
|
||||
}
|
||||
}
|
||||
|
||||
fn is_terminal(fd: BorrowedFd) -> bool {
|
||||
unsafe {
|
||||
libc::isatty(fd.as_raw_fd()) == 1
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ret(input: i32) -> std::io::Result<()> {
|
||||
if input == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for Terminal {
|
||||
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
|
||||
match self {
|
||||
Self::Stdio(stdin) => stdin.as_fd(),
|
||||
Self::File(file) => file.as_fd(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Terminal {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.as_file().read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
|
||||
self.as_file().read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Terminal {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.as_file().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.as_file().flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
|
||||
self.as_file().write_vectored(bufs)
|
||||
}
|
||||
}
|
117
src/sys/windows.rs
Normal file
117
src/sys/windows.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use std::io::{Read, Write};
|
||||
use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
|
||||
|
||||
use winapi::um::consoleapi::{
|
||||
GetConsoleMode,
|
||||
SetConsoleMode,
|
||||
};
|
||||
use winapi::um::wincon::{
|
||||
ENABLE_LINE_INPUT,
|
||||
ENABLE_ECHO_INPUT,
|
||||
};
|
||||
|
||||
use winapi::shared::minwindef::{BOOL, DWORD};
|
||||
|
||||
pub struct Terminal {
|
||||
input: std::io::Stdin,
|
||||
output: std::io::Stderr,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TerminalMode {
|
||||
input_mode: DWORD,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn open() -> std::io::Result<Self> {
|
||||
let input = std::io::stdin();
|
||||
let output = std::io::stderr();
|
||||
if !is_terminal(input.as_handle()) {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "stdin is not a terminal"));
|
||||
}
|
||||
if !is_terminal(output.as_handle()) {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "stderr is not a terminal"));
|
||||
}
|
||||
Ok(Self {
|
||||
input,
|
||||
output,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_terminal_mode(&self) -> std::io::Result<TerminalMode> {
|
||||
unsafe {
|
||||
let mut input_mode = 0;
|
||||
check_ret(GetConsoleMode(self.input.as_raw_handle().cast(), &mut input_mode))?;
|
||||
Ok(TerminalMode {
|
||||
input_mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_terminal_mode(&self, mode: &TerminalMode) -> std::io::Result<()> {
|
||||
unsafe {
|
||||
check_ret(SetConsoleMode(
|
||||
self.input.as_raw_handle().cast(),
|
||||
mode.input_mode,
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalMode {
|
||||
pub fn enable_line_editing(&mut self) {
|
||||
self.input_mode |= ENABLE_LINE_INPUT;
|
||||
}
|
||||
|
||||
pub fn disable_echo(&mut self) {
|
||||
self.input_mode &= !ENABLE_ECHO_INPUT;
|
||||
}
|
||||
|
||||
pub fn enable_echo(&mut self) {
|
||||
self.input_mode |= ENABLE_ECHO_INPUT;
|
||||
}
|
||||
|
||||
pub fn is_echo_enabled(&self) -> bool {
|
||||
self.input_mode & ENABLE_ECHO_INPUT != 0
|
||||
}
|
||||
}
|
||||
|
||||
fn is_terminal(handle: BorrowedHandle) -> bool {
|
||||
unsafe {
|
||||
let mut mode = 0;
|
||||
GetConsoleMode(handle.as_raw_handle().cast(), &mut mode) != 0
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ret(input: BOOL) -> std::io::Result<()> {
|
||||
if input != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Terminal {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.input.read(buf)
|
||||
}
|
||||
|
||||
fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
|
||||
self.input.read_vectored(bufs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Terminal {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.output.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.output.flush()
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
|
||||
self.output.write_vectored(bufs)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue