conpty_oxide/lib.rs
1// SPDX-FileCopyrightText: 2026 conpty-oxide contributors <https://github.com/P4suta/conpty-oxide/graphs/contributors>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! Correctness-first Windows `ConPTY` (pseudoconsole) library.
6//!
7//! `conpty-oxide` wraps the Windows pseudoconsole (`ConPTY`) API with a focus on
8//! getting the hard parts right:
9//!
10//! - A well-defined EOF contract for the console output pipe.
11//! - No hangs around `ClosePseudoConsole`.
12//! - Reliable process-tree termination ("kill tree") via Job objects.
13//! - A blocking API (default `blocking` feature) and an async API behind the
14//! `tokio` feature.
15//! - Dynamic loading of `conpty.dll`, falling back to the system console API.
16//!
17//! This crate targets Windows exclusively and does not compile on other
18//! platforms.
19//!
20//! Low-level lifecycle types, backend identity, and unchecked bundle loading
21//! are intentionally not part of the 0.1 contract. Errors are opaque — there
22//! are no variants to match — and [`Result`] always uses this crate's error.
23//! Hidden compile-fail doctests pin each of these boundaries.
24//!
25//! # Where to start
26//!
27// The two paragraphs below are feature-gated so their intra-doc links always
28// point at something that exists: neither front end is guaranteed to be
29// compiled in, and a link into a module that was configured out is a rustdoc
30// error rather than a dead link.
31#![cfg_attr(
32 feature = "blocking",
33 doc = "[`blocking`] holds the synchronous API. Start a managed session with",
34 doc = "[`blocking::Command::spawn`], then choose [`blocking::Session::wait`]",
35 doc = "when output is unnecessary, [`blocking::Session::collect_output`] to",
36 doc = "retain raw VT, or [`blocking::Session::into_parts`] for independently",
37 doc = "owned I/O, child, and control handles.",
38 doc = ""
39)]
40#![cfg_attr(
41 not(feature = "blocking"),
42 doc = "The `blocking` feature — not enabled in this build of the documentation —",
43 doc = "adds the synchronous `conpty_oxide::blocking` frontend with the three",
44 doc = "managed completion paths: `wait`, `collect_output`, and `into_parts`.",
45 doc = ""
46)]
47#![cfg_attr(
48 feature = "tokio",
49 doc = "The [`tokio`] module mirrors all three paths with `AsyncRead`/`AsyncWrite`",
50 doc = "streams and registered process waits. Frontend types never change meaning",
51 doc = "based on the selected feature: choose `blocking` or `tokio` explicitly.",
52 doc = ""
53)]
54#![cfg_attr(
55 not(feature = "tokio"),
56 doc = "The `tokio` feature — not enabled in this build of the documentation —",
57 doc = "adds a symmetric `conpty_oxide::tokio` frontend.",
58 doc = ""
59)]
60//! # Feature flags
61//!
62//! - `blocking` (default) — the synchronous frontend.
63//! - `tokio` — the asynchronous frontend on Tokio.
64//! - `tracing` — diagnostics through the `tracing` crate; never changes the
65//! public API or any behavior.
66//!
67//! The features can be combined.
68//!
69//! # Managed sessions
70//!
71//! A managed session is bounded by its root process. Once the root's real exit
72//! status is saved, descendants remaining in the session Job are terminated
73//! and the output tail proceeds to EOF. Splitting with `into_parts` changes
74//! ownership only; it does not detach the process tree.
75//!
76//! Input drop or shutdown ends the terminal session rather than delivering an
77//! ordinary stdin EOF. Output is one raw UTF-8/VT byte stream with no separate
78//! stdout and stderr channels. `collect_output` retains an unbounded amount of
79//! output; use `wait` to discard it safely or owned parts to stream it.
80//!
81//! # Choosing a `ConPTY` implementation
82//!
83//! Automatic selection needs no setup: it prefers a validated standalone
84//! `conpty.dll` bundle next to the executable, then falls back to the operating
85//! system's `ConPTY`. An application can also select a bundle explicitly to get
86//! the newer console host's behaviour on older Windows versions:
87//!
88//! - [`ConPtyBackend::auto`] uses a valid bundle found next to the executable,
89//! falls back to the system implementation when that bundle is rejected,
90//! and returns an error if neither is usable. This is also what the default
91//! backend selection does.
92//! - [`ConPtyBackend::from_dir`] loads a bundle from a directory you name,
93//! validating that its `conpty.dll` and `OpenConsole.exe` are a matching
94//! pair before either runs.
95//! - With either frontend enabled, `SessionOptions::backend` selects a backend
96//! for a managed session.
97//!
98//! Cursor inheritance, manual EOF policy, detached sessions, and pre-staged
99//! spawning are intentionally outside the 0.1 API. They can be added later as
100//! typed advanced operations when concrete use cases justify them.
101
102// `cargo test --doc` normally inspects only Rust source, not README.md. Under
103// the all-frontend configuration used by CI, append the README while rustdoc
104// is collecting tests so its blocking, Tokio, and low-level snippets are the
105// exact text compiled. It is omitted from ordinary API documentation and from
106// single/no-frontend doctest legs, where one of those snippets is intentionally
107// unavailable.
108#![cfg_attr(
109 all(doctest, feature = "blocking", feature = "tokio"),
110 doc = include_str!("../README.md")
111)]
112// docs.rs passes `--cfg docsrs` (see `[package.metadata.docs.rs]`), which
113// turns on the nightly-only `doc_cfg` feature: every feature-gated item then
114// carries an "Available on crate feature … only" badge. Stable builds never
115// see the cfg, so this is inert everywhere else. (The badges used to need a
116// separate `doc_auto_cfg` feature; that was merged into `doc_cfg` and removed
117// in 1.92 — rust-lang/rust#138907 — so naming it here breaks the docs.rs
118// build.)
119#![cfg_attr(docsrs, feature(doc_cfg))]
120// Every public item carries documentation, and this keeps it that way under
121// every driver — `cargo rustc`, rustdoc, rust-analyzer — including invocations
122// where Cargo does not forward the workspace lint table.
123#![deny(missing_docs)]
124#![deny(unsafe_op_in_unsafe_fn)]
125
126#[cfg(not(windows))]
127compile_error!(
128 "conpty-oxide only supports Windows targets; \
129 build it with a `*-pc-windows-*` target."
130);
131
132#[cfg(any(feature = "blocking", feature = "tokio"))]
133mod api;
134mod backend;
135#[cfg(any(feature = "blocking", feature = "tokio"))]
136mod command;
137#[cfg(any(feature = "blocking", feature = "tokio", test))]
138mod core;
139mod error;
140mod size;
141mod status;
142
143#[cfg(all(test, feature = "tracing"))]
144mod tracing_test_support;
145
146#[cfg(feature = "blocking")]
147pub mod blocking;
148
149#[cfg(feature = "tokio")]
150pub mod tokio;
151
152#[cfg(any(feature = "blocking", feature = "tokio"))]
153pub use api::{PtyController, SessionOptions, SessionOutput};
154pub use backend::ConPtyBackend;
155pub use error::{BackendError, BackendErrorKind, Error, ErrorKind, Result};
156pub use size::Size;
157pub use status::ExitStatus;
158
159/// The API boundaries stated in the crate docs, pinned as compile-fail
160/// doctests so a widened surface fails `cargo test --doc` instead of
161/// shipping. The module exists only while rustdoc collects doctests, so
162/// none of these render as examples.
163///
164/// Low-level lifecycle types stay private:
165///
166/// ```compile_fail
167/// use conpty_oxide::blocking::Pty;
168/// ```
169///
170/// ```compile_fail
171/// use conpty_oxide::tokio::PtyBuilder;
172/// ```
173///
174/// Backend identity and unchecked bundle loading stay private:
175///
176/// ```compile_fail
177/// use conpty_oxide::BackendKind;
178/// ```
179///
180/// ```compile_fail
181/// let backend = conpty_oxide::ConPtyBackend::from_dir_unchecked(".");
182/// ```
183///
184/// Errors stay opaque and the result alias keeps this crate's error:
185///
186/// ```compile_fail
187/// fn inspect(error: conpty_oxide::Error) {
188/// match error {
189/// conpty_oxide::Error::Io(_) => {}
190/// }
191/// }
192/// ```
193///
194/// ```compile_fail
195/// type ForeignResult = conpty_oxide::Result<(), std::io::Error>;
196/// ```
197#[cfg(doctest)]
198mod api_boundary {}