Skip to main content

rmux_pty/
pty.rs

1use std::io;
2
3#[cfg(unix)]
4use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
5#[cfg(windows)]
6use std::sync::Arc;
7
8use crate::backend;
9#[cfg(all(not(unix), not(windows)))]
10use crate::unsupported_op;
11#[cfg(all(not(unix), not(windows)))]
12use crate::PtyError;
13use crate::{Result, TerminalGeometry, TerminalSize};
14
15#[cfg(unix)]
16/// The slave endpoint of a Unix pseudoterminal pair.
17#[derive(Debug)]
18pub struct PtySlave {
19    fd: OwnedFd,
20}
21
22#[cfg(unix)]
23impl PtySlave {
24    /// Duplicates the slave terminal endpoint.
25    pub fn try_clone(&self) -> Result<Self> {
26        Ok(Self {
27            fd: self.fd.try_clone()?,
28        })
29    }
30
31    /// Consumes the slave endpoint and returns the owned file descriptor.
32    #[must_use]
33    pub fn into_owned_fd(self) -> OwnedFd {
34        self.fd
35    }
36}
37
38#[cfg(unix)]
39impl AsFd for PtySlave {
40    fn as_fd(&self) -> BorrowedFd<'_> {
41        self.fd.as_fd()
42    }
43}
44
45#[cfg(unix)]
46impl AsRawFd for PtySlave {
47    fn as_raw_fd(&self) -> RawFd {
48        self.fd.as_raw_fd()
49    }
50}
51
52/// The I/O endpoint for a pseudoterminal.
53#[derive(Debug)]
54pub struct PtyIo {
55    #[cfg(unix)]
56    fd: OwnedFd,
57    #[cfg(windows)]
58    pty: Arc<backend::WindowsPty>,
59}
60
61impl PtyIo {
62    #[cfg(unix)]
63    pub(crate) fn new(fd: OwnedFd) -> Self {
64        Self { fd }
65    }
66
67    #[cfg(windows)]
68    pub(crate) fn new(pty: Arc<backend::WindowsPty>) -> Self {
69        Self { pty }
70    }
71
72    /// Queries the current terminal geometry for this PTY endpoint.
73    pub fn size(&self) -> Result<TerminalSize> {
74        #[cfg(unix)]
75        {
76            backend::query_size(self.fd.as_fd())
77        }
78
79        #[cfg(not(unix))]
80        {
81            #[cfg(windows)]
82            {
83                backend::query_size(&self.pty)
84            }
85
86            #[cfg(not(windows))]
87            {
88                Err(PtyError::Unsupported(unsupported_op::QUERY_PTY_SIZE))
89            }
90        }
91    }
92
93    /// Resizes this PTY endpoint.
94    pub fn resize(&self, size: TerminalSize) -> Result<()> {
95        #[cfg(unix)]
96        {
97            backend::apply_size(self.fd.as_fd(), size)
98        }
99
100        #[cfg(not(unix))]
101        {
102            #[cfg(windows)]
103            {
104                backend::apply_size(&self.pty, size)
105            }
106
107            #[cfg(not(windows))]
108            {
109                let _ = size;
110                Err(PtyError::Unsupported(unsupported_op::RESIZE_PTY))
111            }
112        }
113    }
114
115    /// Resizes this PTY endpoint, preserving optional pixel geometry where supported.
116    pub fn resize_geometry(&self, geometry: TerminalGeometry) -> Result<()> {
117        #[cfg(unix)]
118        {
119            backend::apply_geometry(self.fd.as_fd(), geometry)
120        }
121
122        #[cfg(not(unix))]
123        {
124            #[cfg(windows)]
125            {
126                backend::apply_geometry(&self.pty, geometry)
127            }
128
129            #[cfg(not(windows))]
130            {
131                let _ = geometry;
132                Err(PtyError::Unsupported(unsupported_op::RESIZE_PTY))
133            }
134        }
135    }
136
137    /// Duplicates this PTY I/O endpoint.
138    pub fn try_clone(&self) -> Result<Self> {
139        #[cfg(unix)]
140        {
141            Ok(Self {
142                fd: self.fd.try_clone()?,
143            })
144        }
145
146        #[cfg(not(unix))]
147        {
148            #[cfg(windows)]
149            {
150                Ok(Self {
151                    pty: Arc::clone(&self.pty),
152                })
153            }
154
155            #[cfg(not(windows))]
156            {
157                Err(PtyError::Unsupported(unsupported_op::CLONE_PTY_IO))
158            }
159        }
160    }
161
162    /// Reads bytes from this PTY endpoint.
163    pub fn read(&self, buffer: &mut [u8]) -> io::Result<usize> {
164        #[cfg(unix)]
165        {
166            backend::read(self.fd.as_fd(), buffer)
167        }
168
169        #[cfg(not(unix))]
170        {
171            #[cfg(windows)]
172            {
173                self.pty.read(buffer)
174            }
175
176            #[cfg(not(windows))]
177            {
178                let _ = buffer;
179                Err(io::Error::new(
180                    io::ErrorKind::Unsupported,
181                    "pty I/O is unsupported on this platform",
182                ))
183            }
184        }
185    }
186
187    /// Writes all bytes to this PTY endpoint.
188    pub fn write_all(&self, bytes: &[u8]) -> io::Result<()> {
189        #[cfg(unix)]
190        {
191            backend::write_all(self.fd.as_fd(), bytes)
192        }
193
194        #[cfg(not(unix))]
195        {
196            #[cfg(windows)]
197            {
198                self.pty.write_all(bytes)
199            }
200
201            #[cfg(not(windows))]
202            {
203                let _ = bytes;
204                Err(io::Error::new(
205                    io::ErrorKind::Unsupported,
206                    "pty I/O is unsupported on this platform",
207                ))
208            }
209        }
210    }
211
212    /// Makes the PTY endpoint nonblocking.
213    pub fn set_nonblocking(&self) -> io::Result<()> {
214        #[cfg(unix)]
215        {
216            backend::set_nonblocking(self.fd.as_fd())
217        }
218
219        #[cfg(not(unix))]
220        {
221            #[cfg(windows)]
222            {
223                Err(io::Error::new(
224                    io::ErrorKind::Unsupported,
225                    "set_nonblocking is not applicable to ConPTY pipe handles; \
226                     async readiness is provided by the Tokio named-pipe driver",
227                ))
228            }
229
230            #[cfg(not(windows))]
231            {
232                Err(io::Error::new(
233                    io::ErrorKind::Unsupported,
234                    "pty I/O is unsupported on this platform",
235                ))
236            }
237        }
238    }
239
240    /// Returns a borrowed Unix descriptor for integration points that still
241    /// require `AsyncFd`.
242    #[cfg(unix)]
243    #[must_use]
244    pub fn as_fd(&self) -> BorrowedFd<'_> {
245        self.fd.as_fd()
246    }
247
248    #[cfg(unix)]
249    pub(crate) fn raw_fd(&self) -> RawFd {
250        self.fd.as_raw_fd()
251    }
252}
253
254#[cfg(unix)]
255impl AsFd for PtyIo {
256    fn as_fd(&self) -> BorrowedFd<'_> {
257        self.fd.as_fd()
258    }
259}
260
261#[cfg(unix)]
262impl AsRawFd for PtyIo {
263    fn as_raw_fd(&self) -> RawFd {
264        self.fd.as_raw_fd()
265    }
266}
267
268/// The master handle of a pseudoterminal.
269#[derive(Debug)]
270pub struct PtyMaster {
271    io: PtyIo,
272}
273
274impl PtyMaster {
275    #[cfg(unix)]
276    pub(crate) fn new(fd: OwnedFd) -> Self {
277        Self { io: PtyIo::new(fd) }
278    }
279
280    #[cfg(windows)]
281    pub(crate) fn new(pty: backend::WindowsPty) -> Self {
282        Self {
283            io: PtyIo::new(Arc::new(pty)),
284        }
285    }
286
287    /// Queries the current terminal geometry for this PTY.
288    pub fn size(&self) -> Result<TerminalSize> {
289        self.io.size()
290    }
291
292    /// Resizes this PTY.
293    pub fn resize(&self, size: TerminalSize) -> Result<()> {
294        self.io.resize(size)
295    }
296
297    /// Resizes this PTY, preserving optional pixel geometry where supported.
298    pub fn resize_geometry(&self, geometry: TerminalGeometry) -> Result<()> {
299        self.io.resize_geometry(geometry)
300    }
301
302    /// Duplicates the master handle.
303    pub fn try_clone(&self) -> Result<Self> {
304        Ok(Self {
305            io: self.io.try_clone()?,
306        })
307    }
308
309    /// Duplicates the master handle as an I/O endpoint.
310    pub fn try_clone_io(&self) -> Result<PtyIo> {
311        self.io.try_clone()
312    }
313
314    /// Consumes this master handle into its I/O endpoint.
315    #[must_use]
316    pub fn into_io(self) -> PtyIo {
317        self.io
318    }
319
320    /// Consumes this Unix PTY master and returns the owned file descriptor.
321    #[cfg(unix)]
322    #[must_use]
323    pub fn into_owned_fd(self) -> OwnedFd {
324        self.io.fd
325    }
326
327    /// Returns the PTY I/O endpoint.
328    #[must_use]
329    pub fn io(&self) -> &PtyIo {
330        &self.io
331    }
332
333    /// Writes all bytes to the PTY master.
334    pub fn write_all(&self, bytes: &[u8]) -> io::Result<()> {
335        self.io.write_all(bytes)
336    }
337
338    #[cfg(unix)]
339    pub(crate) fn raw_fd(&self) -> RawFd {
340        self.io.raw_fd()
341    }
342
343    #[cfg(windows)]
344    pub(crate) fn windows_pty(&self) -> Arc<backend::WindowsPty> {
345        Arc::clone(&self.io.pty)
346    }
347}
348
349/// A freshly allocated PTY pair.
350#[derive(Debug)]
351pub struct PtyPair {
352    master: PtyMaster,
353    #[cfg(unix)]
354    slave: PtySlave,
355}
356
357impl PtyPair {
358    /// Allocates a PTY pair using the platform backend.
359    pub fn open() -> Result<Self> {
360        #[cfg(unix)]
361        {
362            let (master, slave) = backend::open_pty_pair()?;
363
364            Ok(Self {
365                master: PtyMaster::new(master),
366                slave: PtySlave { fd: slave },
367            })
368        }
369
370        #[cfg(windows)]
371        {
372            let master = backend::open_pty_pair(TerminalSize::new(80, 24))?;
373            Ok(Self {
374                master: PtyMaster::new(master),
375            })
376        }
377
378        #[cfg(not(unix))]
379        #[cfg(not(windows))]
380        {
381            Err(PtyError::Unsupported(unsupported_op::OPEN_PTY_PAIR))
382        }
383    }
384
385    /// Allocates a PTY pair and applies an initial window size.
386    pub fn open_with_size(size: TerminalSize) -> Result<Self> {
387        let pair = Self::open()?;
388        pair.master.resize(size)?;
389        Ok(pair)
390    }
391
392    /// Returns the master endpoint.
393    #[must_use]
394    pub fn master(&self) -> &PtyMaster {
395        &self.master
396    }
397
398    /// Returns the slave endpoint.
399    #[cfg(unix)]
400    #[must_use]
401    pub fn slave(&self) -> &PtySlave {
402        &self.slave
403    }
404
405    /// Consumes this Unix PTY pair into its master and slave endpoints.
406    #[cfg(unix)]
407    #[must_use]
408    pub fn into_split(self) -> (PtyMaster, PtySlave) {
409        (self.master, self.slave)
410    }
411
412    /// Consumes the pair and returns the master endpoint.
413    #[must_use]
414    pub fn into_master(self) -> PtyMaster {
415        self.master
416    }
417}