Skip to main content

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