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#[derive(Debug)]
18pub struct PtySlave {
19 fd: OwnedFd,
20}
21
22#[cfg(unix)]
23impl PtySlave {
24 pub fn try_clone(&self) -> Result<Self> {
26 Ok(Self {
27 fd: self.fd.try_clone()?,
28 })
29 }
30
31 #[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#[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 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 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 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 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 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 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 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 #[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#[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 pub fn size(&self) -> Result<TerminalSize> {
289 self.io.size()
290 }
291
292 pub fn resize(&self, size: TerminalSize) -> Result<()> {
294 self.io.resize(size)
295 }
296
297 pub fn resize_geometry(&self, geometry: TerminalGeometry) -> Result<()> {
299 self.io.resize_geometry(geometry)
300 }
301
302 pub fn try_clone(&self) -> Result<Self> {
304 Ok(Self {
305 io: self.io.try_clone()?,
306 })
307 }
308
309 pub fn try_clone_io(&self) -> Result<PtyIo> {
311 self.io.try_clone()
312 }
313
314 #[must_use]
316 pub fn into_io(self) -> PtyIo {
317 self.io
318 }
319
320 #[cfg(unix)]
322 #[must_use]
323 pub fn into_owned_fd(self) -> OwnedFd {
324 self.io.fd
325 }
326
327 #[must_use]
329 pub fn io(&self) -> &PtyIo {
330 &self.io
331 }
332
333 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#[derive(Debug)]
351pub struct PtyPair {
352 master: PtyMaster,
353 #[cfg(unix)]
354 slave: PtySlave,
355}
356
357impl PtyPair {
358 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 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 #[must_use]
394 pub fn master(&self) -> &PtyMaster {
395 &self.master
396 }
397
398 #[cfg(unix)]
400 #[must_use]
401 pub fn slave(&self) -> &PtySlave {
402 &self.slave
403 }
404
405 #[cfg(unix)]
407 #[must_use]
408 pub fn into_split(self) -> (PtyMaster, PtySlave) {
409 (self.master, self.slave)
410 }
411
412 #[must_use]
414 pub fn into_master(self) -> PtyMaster {
415 self.master
416 }
417}