Skip to main content

conpty_oxide/
api.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//! Public types shared by the blocking and Tokio front ends.
6
7use std::fmt;
8#[cfg(any(feature = "blocking", feature = "tokio"))]
9use std::sync::Arc;
10
11use crate::backend::ConPtyBackend;
12#[cfg(any(feature = "blocking", feature = "tokio"))]
13use crate::core::session::Session as SessionCore;
14use crate::error::Result;
15use crate::size::Size;
16use crate::status::ExitStatus;
17
18/// Safe configuration for a managed pseudoconsole session.
19///
20/// Managed sessions deliberately expose only the initial terminal size and
21/// backend choice. Cursor inheritance, manual EOF policy, and detached
22/// spawning are outside the 0.1 API.
23#[derive(Debug, Clone, Default)]
24pub struct SessionOptions {
25    size: Size,
26    backend: Option<ConPtyBackend>,
27}
28
29impl SessionOptions {
30    /// Creates options with an 80x24 terminal and automatic backend selection.
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Sets the initial terminal size.
37    #[must_use]
38    pub const fn size(mut self, size: Size) -> Self {
39        self.size = size;
40        self
41    }
42
43    /// Selects a `ConPTY` backend for this session.
44    #[must_use]
45    pub fn backend(mut self, backend: ConPtyBackend) -> Self {
46        self.backend = Some(backend);
47        self
48    }
49
50    #[cfg(any(feature = "blocking", feature = "tokio"))]
51    #[must_use]
52    pub(super) fn into_parts(self) -> (Size, Option<ConPtyBackend>) {
53        (self.size, self.backend)
54    }
55}
56
57/// Virtual-terminal output collected from a managed session.
58///
59/// `ConPTY` exposes one rendered VT byte stream rather than distinct stdout and
60/// stderr channels, so this type intentionally does not pretend otherwise.
61///
62/// The byte buffer may be large, so collecting output does not also make it
63/// implicitly cloneable:
64///
65/// ```compile_fail
66/// use conpty_oxide::SessionOutput;
67///
68/// fn require_clone<T: Clone>() {}
69/// require_clone::<SessionOutput>();
70/// ```
71pub struct SessionOutput {
72    status: ExitStatus,
73    bytes: Vec<u8>,
74}
75
76impl SessionOutput {
77    #[cfg(any(feature = "blocking", feature = "tokio"))]
78    #[must_use]
79    pub(super) const fn new(status: ExitStatus, bytes: Vec<u8>) -> Self {
80        Self { status, bytes }
81    }
82
83    /// Returns the root process's exit status.
84    #[must_use]
85    pub const fn status(&self) -> ExitStatus {
86        self.status
87    }
88
89    /// Borrows the rendered UTF-8/VT byte stream.
90    #[must_use]
91    pub fn as_bytes(&self) -> &[u8] {
92        &self.bytes
93    }
94
95    /// Consumes the result and returns the rendered UTF-8/VT byte stream.
96    #[must_use]
97    pub fn into_bytes(self) -> Vec<u8> {
98        self.bytes
99    }
100}
101
102impl fmt::Debug for SessionOutput {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.debug_struct("SessionOutput")
105            .field("status", &self.status)
106            .field("bytes", &format_args!("{} bytes", self.bytes.len()))
107            .finish()
108    }
109}
110
111/// Cloneable control handle shared by both public front ends.
112///
113/// It contains no pipe or runtime-specific state. Clones may be used from any
114/// thread, and the pseudoconsole remains alive while the controller or either
115/// owned I/O half still exists.
116#[derive(Clone)]
117pub struct PtyController {
118    #[cfg(any(feature = "blocking", feature = "tokio"))]
119    pub(super) inner: Arc<SessionCore>,
120    #[cfg(not(any(feature = "blocking", feature = "tokio")))]
121    uninhabited: std::convert::Infallible,
122}
123
124impl PtyController {
125    #[cfg(any(feature = "blocking", feature = "tokio"))]
126    pub(super) const fn new(inner: Arc<SessionCore>) -> Self {
127        Self { inner }
128    }
129}
130
131#[cfg(any(feature = "blocking", feature = "tokio"))]
132impl PtyController {
133    /// Resizes the pseudoconsole.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error with [`crate::ErrorKind::Resize`] when the session is
138    /// closed or the backend rejects the requested size.
139    pub fn resize(&self, size: Size) -> Result<()> {
140        self.inner.resize(size)
141    }
142
143    /// Returns the last successfully applied terminal size.
144    #[must_use]
145    pub fn size(&self) -> Size {
146        self.inner.size()
147    }
148
149    /// Clears the pseudoconsole screen and scrollback.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error with [`crate::ErrorKind::UnsupportedFeature`] when
154    /// clearing is unavailable, or [`crate::ErrorKind::Clear`] when the
155    /// operation fails.
156    pub fn clear(&self) -> Result<()> {
157        self.inner.clear()
158    }
159
160    /// Returns whether this backend provides `ClearPseudoConsole`.
161    #[must_use]
162    pub fn supports_clear(&self) -> bool {
163        self.inner.supports_clear()
164    }
165
166    #[must_use]
167    #[cfg(test)]
168    pub(crate) fn supports_release(&self) -> bool {
169        self.inner.supports_release()
170    }
171
172    #[must_use]
173    #[cfg(test)]
174    pub(crate) fn reader_finished(&self) -> bool {
175        self.inner.reader_finished()
176    }
177
178    #[must_use]
179    #[cfg(test)]
180    pub(crate) fn backend_kind(&self) -> &crate::backend::BackendKind {
181        self.inner.backend_kind()
182    }
183}
184
185#[cfg(any(feature = "blocking", feature = "tokio"))]
186impl fmt::Debug for PtyController {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_struct("PtyController")
189            .field("size", &self.inner.size())
190            .field("supports_clear", &self.inner.supports_clear())
191            .finish_non_exhaustive()
192    }
193}