Skip to content

Commit

Permalink
complete first draft of Windows implementation; uplift options to hav…
Browse files Browse the repository at this point in the history
…e a builder and be read-only after build(); improve debuggability
  • Loading branch information
apparebit committed Nov 12, 2024
1 parent 1aa21b3 commit fa8cb9f
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 175 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ version = "0.59.0"
optional = true
features = [
"Win32_Foundation",
"Win32_Globalization",
"Win32_System_Console",
"Win32_System_Threading",
]


[features]
default = ["f64", "term"]
f64 = []
Expand Down
11 changes: 5 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,12 @@ feature disabled, [on Docs.rs](https://docs.rs/prettypretty/latest/prettypretty/
//! for doing so.
//! * The optional [`term`] module supports just enough **terminal I/O** to
//! query a terminal for the current color theme, which is required for
//! instantiating a `Translator`. Notably, the Unix-only
//! instantiating a `Translator`. Notably,
//! [`Terminal`](crate::term::Terminal) type configures the terminal to use
//! raw mode and to time out reads. Meanwhile,
//! [`VtScanner`](crate::term::VtScanner) implements the complete state
//! machine for **parsing ANSI escape sequences**. Applications that require
//! support for Windows or async I/O should bring their own, more
//! fully-featured terminal crate.
//! machine for **parsing ANSI escape sequences**. Both Unix and Windows are
//! supported but Windows support is largely untested.
#![cfg_attr(
feature = "gamut",
doc = " * The optional [`gamut`] and [`spectrum`] submodules enable the traversal
Expand Down Expand Up @@ -144,8 +143,8 @@ feature disabled, [on Docs.rs](https://docs.rs/prettypretty/latest/prettypretty/
//! ### 2. Adjust Your Styles
//!
//! Second, determine the terminal's current color theme with
//! [`Theme::query_terminal`](trans::Theme::query_terminal) (sorry, but
//! Unix-only for now) and `stdout`'s color support with
//! [`Theme::query_terminal`](trans::Theme::query_terminal)
//! and `stdout`'s color support with
//! [`Fidelity::from_environment`](style::Fidelity::from_environment).
//!
//! ```
Expand Down
64 changes: 47 additions & 17 deletions src/term/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
//!
//! When combined with [`Theme`](crate::trans::Theme) and
//! [`ThemeEntry`](crate::trans::ThemeEntry), querying the terminal for its
//! color theme looks straightforward. Let's walk through one possible solution.
//! color theme becomes fairly straightforward. Let's walk through one possible
//! solution.
//!
//! # Examples
//!
//! ## 1. Taking Care of Not-Unix
//! ## 1. Scaffolding
//!
//! First, since terminal support is limited to Unix only, lets set up two
//! versions of the same function, one for other platforms and one for Unix.
//! Since we are assembling a color theme by interacting with the terminal,
//! `std::io::Result<Theme>` seems like a good result type, with
//! [`Theme`](crate::trans::Theme) collecting the two default and 16 ANSI colors
//! belonging to the, ahem, theme:
//! Windows support is largely untested. So for now, we run the example code on
//! Unix only. Prettypretty has a dedicated type for a color
//! [`Theme`](crate::trans::Theme), which collects the two default and 16 ANSI
//! colors. Since we are interacting with the terminal to generate an instance,
//! `std::io::Result<Theme>` seems like a good result type for our function.
//!
//! ```
//! # use std::io::{ErrorKind, Result};
Expand Down Expand Up @@ -254,11 +254,11 @@
//!
//! # Background
//!
//! Integrating terminal I/O is trivial, as long as an application does not need
//! to read terminal input: The application simply writes text and ANSI escape
//! sequences to style the text to standard output or error. For just that
//! reason, the display of [`Style`](crate::style::Style) is the ANSI escape
//! sequence that changes the terminal to use that style.
//! Integrating terminal I/O is trivial on Unix, as long as an application does
//! not need to read terminal input: The application simply writes text and ANSI
//! escape sequences to style the text to standard output or error. For just
//! that reason, the display of [`Style`](crate::style::Style) is the ANSI
//! escape sequence that changes the terminal to use that style.
//!
//!
//! ## 1. Gnarly Input
Expand Down Expand Up @@ -291,10 +291,9 @@
//! a lean but functional terminal integration layer.
//!
//! However, they won't meet all application needs. Notably, if your application
//! requires Windows support or async I/O, please consider using a more
//! fully-featured terminal crate such as
//! [Crossterm](https://github.com/crossterm-rs/crossterm). For the same reason,
//! this module is option and requires the `term` feature.
//! requires async I/O, please consider using a more fully-featured terminal
//! crate such as [Crossterm](https://github.com/crossterm-rs/crossterm). For
//! the same reason, this module is optional and requires the `term` feature.
//!
//!
//! ## 2. Ways to Time Out Reads
Expand Down Expand Up @@ -336,6 +335,37 @@
//! A third pitfall is that Rust turns read operations that return zero bytes
//! into end-of-file errors. This module helps to mitigate those errors, but an
//! application may need to detect them as well.
//!
//!
//! ## 3. Windows
//!
//! Starting with Windows 10 TH2 (v1511), Windows also supports ANSI escape
//! sequences. While applications currently need to explicitly enable virtual
//! terminal processing, they also are the preferred means for interacting with
//! the console host moving forward, and several console functions that provide
//! equivalent functionality have been deprecated.
//!
//! The Windows console host provides much of the same functionality as Unix
//! pseudo-terminals with a completely different API. Two differences stick out:
//!
//! 1. To interact with the terminal independent of redirected streams, a Unix
//! application connects to one endpoint by opening `/dev/tty`. Windows has
//! independent abstractions for input and output, with the equivalent code
//! on Windows opening `CONIN$` and `CONOUT$`. As a direct consequence,
//! configuring the console on Windows also requires reading and writing two
//! modes.
//! 2. The Windows API duplicates a large number of functions, with the name
//! ending either in `A` or `W`, e.g., `ReadConsoleA` vs `ReadConsoleW`. The
//! former represent strings with 8-bit characters, whose encoding is
//! determined by the current "code page". The latter represent strings as
//! UTF-16. Thankfully, there is a code page for UTF-8, but that does
//! require reading and writing the code page for input and output during
//! the initial configuration.
//!
//! Meanwhile, timing out reads is easy. `WaitForSingleObject` takes only two
//! arguments, the handle for console input and the timeout, and gets the job
//! done. Potentially adding a future waker is easy as well: Just switch to
//! `WaitForMultipleObjects`.
mod escape;
mod render;
Expand Down
35 changes: 24 additions & 11 deletions src/term/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! same reason, [`TerminalConfig`], [`TerminalReader`], and [`TerminalWriter`]
//! must not be directly exposed to application code.
use std::ffi::c_void;
use std::fs::OpenOptions;
use std::io::{Read, Result, Write};
use std::os::fd::{AsRawFd, OwnedFd};
Expand Down Expand Up @@ -76,11 +77,13 @@ impl Device {
}

/// Get process group ID.
#[inline]
pub fn pid(&self) -> Result<u32> {
unsafe { libc::tcgetsid(self.fd.as_raw_fd()) }.into_result()
}

/// Get a handle for the device.
#[inline]
pub fn handle(&self) -> DeviceHandle {
DeviceHandle(self.fd.as_raw_fd())
}
Expand Down Expand Up @@ -144,12 +147,14 @@ impl Termios {
}

impl AsRef<libc::termios> for Termios {
#[inline]
fn as_ref(&self) -> &libc::termios {
&self.inner
}
}

impl AsMut<libc::termios> for Termios {
#[inline]
fn as_mut(&mut self) -> &mut libc::termios {
&mut self.inner
}
Expand All @@ -158,12 +163,9 @@ impl AsMut<libc::termios> for Termios {
impl std::fmt::Debug for Termios {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Determine enabled flags
let mut flags = String::new();
let mut flags = Vec::new();
let mut append = |s| {
if !flags.is_empty() {
flags.push_str(", ");
}
flags.push_str(s);
flags.push(s);
};

for (name, value) in [
Expand Down Expand Up @@ -207,8 +209,16 @@ impl std::fmt::Debug for Termios {
}
}

struct Flags(Vec<&'static str>);

impl std::fmt::Debug for Flags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.0.iter()).finish()
}
}

f.debug_struct("Termios")
.field("flags", &flags)
.field("flags", &Flags(flags))
.field("vmin", &self.inner.c_cc[libc::VMIN])
.field("vtime", &self.inner.c_cc[libc::VTIME])
.finish()
Expand Down Expand Up @@ -282,7 +292,7 @@ impl Config {
let mut wrapper = attributes.clone();
let inner = wrapper.as_mut();

match options.mode {
match options.mode() {
Mode::Rare => {
inner.c_lflag &= !(libc::ECHO | libc::ICANON);
}
Expand All @@ -292,7 +302,7 @@ impl Config {
}

inner.c_cc[libc::VMIN] = 0;
inner.c_cc[libc::VTIME] = options.timeout.get();
inner.c_cc[libc::VTIME] = options.timeout().get();
wrapper
}

Expand All @@ -319,7 +329,10 @@ pub(crate) struct Reader {

impl Reader {
/// Create a new reader with a raw file descriptor.
pub fn new(handle: RawHandle) -> Self {
///
/// The second parameter is only used on Windows. Adding it for Unix too
/// simplifies the code instantiating the reader.
pub fn new(handle: RawHandle, _: u32) -> Self {
Self { handle }
}
}
Expand All @@ -329,7 +342,7 @@ impl Read for Reader {
unsafe {
libc::read(
self.handle,
buf.as_mut_ptr() as *mut libc::c_void,
buf.as_mut_ptr() as *mut c_void,
buf.len() as libc::size_t,
)
}
Expand Down Expand Up @@ -362,7 +375,7 @@ impl Write for Writer {
unsafe {
libc::write(
self.handle,
buf.as_ptr() as *mut libc::c_void,
buf.as_ptr() as *const c_void,
buf.len() as libc::size_t,
)
}
Expand Down
Loading

0 comments on commit fa8cb9f

Please sign in to comment.