Skip to main content

uncurses/terminal/
stdio.rs

1//! Direct, unbuffered handles for inherited standard streams.
2//!
3//! [`Stdout`], [`Stderr`], and [`Stdin`] mirror [`std::io::stdout`],
4//! [`std::io::stderr`], and [`std::io::stdin`] for terminal I/O, but avoid the
5//! standard library's line writer and input buffering. They are cheap `Copy`
6//! handles over process-wide stream state and are suitable for renderers that
7//! want explicit control over flushing and buffering.
8//!
9//! ## Behavior relative to `std::io`
10//!
11//! * **Process-wide singletons with a shared lock.** Every call to
12//!   [`stdout`] / [`stderr`] / [`stdin`] returns a fresh handle that
13//!   references the same underlying [`Mutex`]-guarded raw stream.
14//!   Cloning a handle is free; only the [`Mutex`] is shared, so
15//!   concurrent writes from multiple threads are serialised and do
16//!   not interleave at the byte level.
17//! * **No line buffering on writes.** A call to [`Write::write`] is
18//!   forwarded to a single OS write call, regardless of any `\n`
19//!   present in the buffer.
20//! * **Unbuffered.** No reads or writes are batched. Wrap in
21//!   [`std::io::BufWriter`] / [`std::io::BufReader`] to coalesce
22//!   syscalls.
23//! * **Same Windows console semantics.** When the inherited handle
24//!   refers to a console, output is transcoded UTF-8 → UTF-16 and
25//!   delivered through `WriteConsoleW`, and input is read with
26//!   `ReadConsoleW` and transcoded UTF-16 → UTF-8 — matching what
27//!   `std::io::Stdout` / `std::io::Stdin` do. UTF-8 sequences split
28//!   across calls are carried forward via a 4-byte per-stream buffer.
29//! * **Independent of `std`'s lock.** The shared [`Mutex`] used here
30//!   is separate from the one inside [`std::io::Stdout`] /
31//!   [`std::io::Stdin`], so concurrent `println!` / panic output may
32//!   still interleave with ours. TUI applications that own the
33//!   screen typically route diagnostic output away from stdout to
34//!   avoid this.
35//! * **Non-reentrant lock.** Writing to the same stream from inside
36//!   a write call (for example, from a panic hook that prints to
37//!   stdout while a write is in progress on the same thread) will
38//!   deadlock. The std counterparts use a reentrant lock to avoid
39//!   this; we accept the limitation in exchange for keeping the
40//!   surface area small.
41//!
42//! ## Usage
43//!
44//! Use the free functions when constructing [`Terminal`](super::Terminal) or
45//! when writing directly to the inherited streams. Wrap a handle in
46//! [`std::io::BufWriter`] if many small writes should be coalesced.
47//!
48//! Each [`Stdout`] / [`Stderr`] / [`Stdin`] is a borrowed view of the
49//! inherited descriptor; dropping it does not close that descriptor.
50
51use std::io::{self, IoSlice, Read, Write};
52use std::sync::{Mutex, MutexGuard, OnceLock};
53
54#[cfg(unix)]
55use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
56
57#[cfg(windows)]
58use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
59
60// ---------------------------------------------------------------------------
61// Process-wide singletons
62// ---------------------------------------------------------------------------
63
64static STDOUT: OnceLock<Mutex<StdoutRaw>> = OnceLock::new();
65static STDERR: OnceLock<Mutex<StderrRaw>> = OnceLock::new();
66static STDIN: OnceLock<Mutex<StdinRaw>> = OnceLock::new();
67
68fn stdout_lock() -> &'static Mutex<StdoutRaw> {
69    STDOUT.get_or_init(|| Mutex::new(StdoutRaw(imp::out())))
70}
71fn stderr_lock() -> &'static Mutex<StderrRaw> {
72    STDERR.get_or_init(|| Mutex::new(StderrRaw(imp::err())))
73}
74fn stdin_lock() -> &'static Mutex<StdinRaw> {
75    STDIN.get_or_init(|| Mutex::new(StdinRaw(imp::input())))
76}
77
78/// Return a handle to inherited standard output.
79///
80/// Every call returns a cheap `Copy` handle referencing the same process-wide
81/// stdout state. Writes through this handle are unbuffered except for the
82/// platform console transcoding described in the module-level documentation.
83///
84/// # Returns
85///
86/// A [`Stdout`] handle.
87///
88/// # Errors and panics
89///
90/// This function does not fail or intentionally panic. I/O errors are reported
91/// by the handle's [`Write`] implementation.
92pub fn stdout() -> Stdout {
93    Stdout {
94        inner: stdout_lock(),
95    }
96}
97
98/// Return a handle to inherited standard error.
99///
100/// Every call returns a cheap `Copy` handle referencing the same process-wide
101/// stderr state. Writes are unbuffered and serialized through this module's
102/// stderr lock.
103///
104/// # Returns
105///
106/// A [`Stderr`] handle.
107///
108/// # Errors and panics
109///
110/// This function does not fail or intentionally panic. I/O errors are reported
111/// by the handle's [`Write`] implementation.
112pub fn stderr() -> Stderr {
113    Stderr {
114        inner: stderr_lock(),
115    }
116}
117
118/// Return a handle to inherited standard input.
119///
120/// Every call returns a cheap `Copy` handle referencing the same process-wide
121/// stdin state. Reads are unbuffered and serialized through this module's
122/// stdin lock.
123///
124/// # Returns
125///
126/// A [`Stdin`] handle.
127///
128/// # Errors and panics
129///
130/// This function does not fail or intentionally panic. I/O errors are reported
131/// by the handle's [`Read`] implementation.
132pub fn stdin() -> Stdin {
133    Stdin {
134        inner: stdin_lock(),
135    }
136}
137
138// ---------------------------------------------------------------------------
139// Raw, unbuffered, unlocked streams (private)
140// ---------------------------------------------------------------------------
141
142struct StdoutRaw(imp::Output);
143struct StderrRaw(imp::Output);
144struct StdinRaw(imp::Input);
145
146impl Write for StdoutRaw {
147    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
148        self.0.write(buf)
149    }
150    fn flush(&mut self) -> io::Result<()> {
151        self.0.flush()
152    }
153    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
154        self.0.write_vectored(bufs)
155    }
156}
157
158impl Write for StderrRaw {
159    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
160        self.0.write(buf)
161    }
162    fn flush(&mut self) -> io::Result<()> {
163        self.0.flush()
164    }
165    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
166        self.0.write_vectored(bufs)
167    }
168}
169
170impl Read for StdinRaw {
171    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
172        self.0.read(buf)
173    }
174}
175
176// ---------------------------------------------------------------------------
177// Public handle types
178// ---------------------------------------------------------------------------
179
180/// Handle to the inherited standard output stream.
181///
182/// `Stdout` is `Copy`; copying it creates another view of the same
183/// process-wide stream and lock. Dropping it does not close file descriptor 1
184/// or the Windows standard output handle.
185///
186/// Use [`lock`](Stdout::lock) to hold the stdout lock across multiple writes,
187/// or write directly to the handle for per-call locking. See the
188/// module-level documentation for buffering and Windows console behavior.
189#[derive(Clone, Copy)]
190pub struct Stdout {
191    inner: &'static Mutex<StdoutRaw>,
192}
193
194/// Handle to the inherited standard error stream.
195///
196/// `Stderr` is `Copy`; copying it creates another view of the same
197/// process-wide stream and lock. Dropping it does not close file descriptor 2
198/// or the Windows standard error handle.
199///
200/// Use [`lock`](Stderr::lock) to hold the stderr lock across multiple writes,
201/// or write directly to the handle for per-call locking.
202#[derive(Clone, Copy)]
203pub struct Stderr {
204    inner: &'static Mutex<StderrRaw>,
205}
206
207/// Handle to the inherited standard input stream.
208///
209/// `Stdin` is `Copy`; copying it creates another view of the same process-wide
210/// stream and lock. Dropping it does not close file descriptor 0 or the
211/// Windows standard input handle.
212///
213/// Use [`lock`](Stdin::lock) to hold the stdin lock across multiple reads, or
214/// read directly from the handle for per-call locking.
215#[derive(Clone, Copy)]
216pub struct Stdin {
217    inner: &'static Mutex<StdinRaw>,
218}
219
220impl Stdout {
221    /// Acquire stdout's shared write lock.
222    ///
223    /// While the returned guard is held, no other [`Stdout`] handle can write
224    /// through this module. The lock is not reentrant; attempting to write to
225    /// stdout through this module again on the same thread while holding the
226    /// guard will deadlock.
227    ///
228    /// # Returns
229    ///
230    /// A [`StdoutLock`] that implements [`Write`].
231    ///
232    /// # Errors and panics
233    ///
234    /// This method does not return errors. If another thread panicked while
235    /// holding the mutex, the poisoned lock is recovered and the guard is still
236    /// returned.
237    pub fn lock(&self) -> StdoutLock<'static> {
238        StdoutLock {
239            guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
240        }
241    }
242}
243
244impl Stderr {
245    /// Acquire stderr's shared write lock.
246    ///
247    /// While the returned guard is held, no other [`Stderr`] handle can write
248    /// through this module. The lock is not reentrant; attempting to write to
249    /// stderr through this module again on the same thread while holding the
250    /// guard will deadlock.
251    ///
252    /// # Returns
253    ///
254    /// A [`StderrLock`] that implements [`Write`].
255    ///
256    /// # Errors and panics
257    ///
258    /// This method does not return errors. Poisoned locks are recovered.
259    pub fn lock(&self) -> StderrLock<'static> {
260        StderrLock {
261            guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
262        }
263    }
264}
265
266impl Stdin {
267    /// Acquire stdin's shared read lock.
268    ///
269    /// While the returned guard is held, no other [`Stdin`] handle can read
270    /// through this module. The lock is not reentrant; attempting to read from
271    /// stdin through this module again on the same thread while holding the
272    /// guard will deadlock.
273    ///
274    /// # Returns
275    ///
276    /// A [`StdinLock`] that implements [`Read`].
277    ///
278    /// # Errors and panics
279    ///
280    /// This method does not return errors. Poisoned locks are recovered.
281    pub fn lock(&self) -> StdinLock<'static> {
282        StdinLock {
283            guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
284        }
285    }
286}
287
288// ---------------------------------------------------------------------------
289// Lock guards
290// ---------------------------------------------------------------------------
291
292/// Locked, exclusive access to [`Stdout`].
293///
294/// The guard implements [`Write`] and releases the stdout lock when dropped.
295/// Holding it across several writes prevents interleaving with other writes
296/// performed through this module.
297pub struct StdoutLock<'a> {
298    guard: MutexGuard<'a, StdoutRaw>,
299}
300
301/// Locked, exclusive access to [`Stderr`].
302///
303/// The guard implements [`Write`] and releases the stderr lock when dropped.
304pub struct StderrLock<'a> {
305    guard: MutexGuard<'a, StderrRaw>,
306}
307
308/// Locked, exclusive access to [`Stdin`].
309///
310/// The guard implements [`Read`] and releases the stdin lock when dropped.
311pub struct StdinLock<'a> {
312    guard: MutexGuard<'a, StdinRaw>,
313}
314
315impl Write for StdoutLock<'_> {
316    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
317        self.guard.write(buf)
318    }
319    fn flush(&mut self) -> io::Result<()> {
320        self.guard.flush()
321    }
322    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
323        self.guard.write_vectored(bufs)
324    }
325}
326
327impl Write for StderrLock<'_> {
328    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
329        self.guard.write(buf)
330    }
331    fn flush(&mut self) -> io::Result<()> {
332        self.guard.flush()
333    }
334    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
335        self.guard.write_vectored(bufs)
336    }
337}
338
339impl Read for StdinLock<'_> {
340    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
341        self.guard.read(buf)
342    }
343}
344
345// ---------------------------------------------------------------------------
346// Write/Read impls on the handle types
347//
348// Each call locks the shared mutex briefly, performs the I/O, and
349// releases. Both `&mut self` and `&self` impls are provided so the
350// handles can be used with `write!`/`writeln!` macros via either an
351// owned binding or a shared reference.
352// ---------------------------------------------------------------------------
353
354impl Write for Stdout {
355    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
356        self.lock().write(buf)
357    }
358    fn flush(&mut self) -> io::Result<()> {
359        self.lock().flush()
360    }
361    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
362        self.lock().write_vectored(bufs)
363    }
364}
365
366impl Write for &Stdout {
367    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
368        self.lock().write(buf)
369    }
370    fn flush(&mut self) -> io::Result<()> {
371        self.lock().flush()
372    }
373    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
374        self.lock().write_vectored(bufs)
375    }
376}
377
378impl Write for Stderr {
379    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
380        self.lock().write(buf)
381    }
382    fn flush(&mut self) -> io::Result<()> {
383        self.lock().flush()
384    }
385    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
386        self.lock().write_vectored(bufs)
387    }
388}
389
390impl Write for &Stderr {
391    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
392        self.lock().write(buf)
393    }
394    fn flush(&mut self) -> io::Result<()> {
395        self.lock().flush()
396    }
397    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
398        self.lock().write_vectored(bufs)
399    }
400}
401
402impl Read for Stdin {
403    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
404        self.lock().read(buf)
405    }
406}
407
408impl Read for &Stdin {
409    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
410        self.lock().read(buf)
411    }
412}
413
414// ---------------------------------------------------------------------------
415// AsFd / AsRawFd / AsHandle / AsRawHandle
416//
417// Implemented on every handle and guard type. The underlying
418// descriptor / handle is process-stable, so these are lock-free and
419// always reference the inherited stream.
420// ---------------------------------------------------------------------------
421
422#[cfg(unix)]
423const STDOUT_FD: RawFd = libc::STDOUT_FILENO;
424#[cfg(unix)]
425const STDERR_FD: RawFd = libc::STDERR_FILENO;
426#[cfg(unix)]
427const STDIN_FD: RawFd = libc::STDIN_FILENO;
428
429#[cfg(unix)]
430macro_rules! impl_as_fd {
431    ($ty:ty, $fd:expr) => {
432        impl AsFd for $ty {
433            fn as_fd(&self) -> BorrowedFd<'_> {
434                // SAFETY: the standard descriptors are inherited from
435                // the parent and remain valid for the lifetime of the
436                // process; the returned borrow is bounded by `&self`.
437                unsafe { BorrowedFd::borrow_raw($fd) }
438            }
439        }
440        impl AsRawFd for $ty {
441            fn as_raw_fd(&self) -> RawFd {
442                $fd
443            }
444        }
445    };
446}
447
448#[cfg(unix)]
449impl_as_fd!(Stdout, STDOUT_FD);
450#[cfg(unix)]
451impl_as_fd!(Stderr, STDERR_FD);
452#[cfg(unix)]
453impl_as_fd!(Stdin, STDIN_FD);
454#[cfg(unix)]
455impl_as_fd!(StdoutLock<'_>, STDOUT_FD);
456#[cfg(unix)]
457impl_as_fd!(StderrLock<'_>, STDERR_FD);
458#[cfg(unix)]
459impl_as_fd!(StdinLock<'_>, STDIN_FD);
460
461#[cfg(windows)]
462macro_rules! impl_as_handle {
463    ($ty:ty, $which:expr) => {
464        impl AsHandle for $ty {
465            fn as_handle(&self) -> BorrowedHandle<'_> {
466                // SAFETY: the standard handles are inherited from the
467                // parent and remain valid for the lifetime of the
468                // process; the returned borrow is bounded by `&self`.
469                let h = unsafe { ::windows_sys::Win32::System::Console::GetStdHandle($which) };
470                unsafe { BorrowedHandle::borrow_raw(h as _) }
471            }
472        }
473        impl AsRawHandle for $ty {
474            fn as_raw_handle(&self) -> RawHandle {
475                let h = unsafe { ::windows_sys::Win32::System::Console::GetStdHandle($which) };
476                h as RawHandle
477            }
478        }
479    };
480}
481
482#[cfg(windows)]
483impl_as_handle!(
484    Stdout,
485    ::windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE
486);
487#[cfg(windows)]
488impl_as_handle!(
489    Stderr,
490    ::windows_sys::Win32::System::Console::STD_ERROR_HANDLE
491);
492#[cfg(windows)]
493impl_as_handle!(
494    Stdin,
495    ::windows_sys::Win32::System::Console::STD_INPUT_HANDLE
496);
497#[cfg(windows)]
498impl_as_handle!(
499    StdoutLock<'_>,
500    ::windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE
501);
502#[cfg(windows)]
503impl_as_handle!(
504    StderrLock<'_>,
505    ::windows_sys::Win32::System::Console::STD_ERROR_HANDLE
506);
507#[cfg(windows)]
508impl_as_handle!(
509    StdinLock<'_>,
510    ::windows_sys::Win32::System::Console::STD_INPUT_HANDLE
511);
512
513// ---------------------------------------------------------------------------
514// Unix implementation
515// ---------------------------------------------------------------------------
516
517#[cfg(unix)]
518mod imp {
519    use std::fs::File;
520    use std::io::{self, Read, Write};
521    use std::mem::ManuallyDrop;
522    use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
523
524    pub(super) fn out() -> Output {
525        Output::new(libc::STDOUT_FILENO)
526    }
527    pub(super) fn err() -> Output {
528        Output::new(libc::STDERR_FILENO)
529    }
530    pub(super) fn input() -> Input {
531        Input::new(libc::STDIN_FILENO)
532    }
533
534    pub(super) struct Output {
535        // Borrowed view of an inherited fd; `ManuallyDrop` keeps `File`
536        // from closing the descriptor when the wrapper is dropped.
537        file: ManuallyDrop<File>,
538    }
539
540    pub(super) struct Input {
541        file: ManuallyDrop<File>,
542    }
543
544    impl Output {
545        fn new(fd: RawFd) -> Self {
546            // SAFETY: fd 1 / 2 are inherited from the parent process
547            // and remain valid for its lifetime; `ManuallyDrop`
548            // prevents `File::drop` from calling `close(2)`.
549            Self {
550                file: ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }),
551            }
552        }
553    }
554
555    impl Input {
556        fn new(fd: RawFd) -> Self {
557            // SAFETY: see `Output::new`.
558            Self {
559                file: ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }),
560            }
561        }
562    }
563
564    impl Write for Output {
565        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
566            (&*self.file).write(buf)
567        }
568        fn flush(&mut self) -> io::Result<()> {
569            (&*self.file).flush()
570        }
571        fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
572            (&*self.file).write_vectored(bufs)
573        }
574    }
575
576    impl Read for Input {
577        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
578            (&*self.file).read(buf)
579        }
580    }
581
582    impl AsFd for Output {
583        fn as_fd(&self) -> BorrowedFd<'_> {
584            self.file.as_fd()
585        }
586    }
587    impl AsRawFd for Output {
588        fn as_raw_fd(&self) -> RawFd {
589            self.file.as_raw_fd()
590        }
591    }
592    impl AsFd for Input {
593        fn as_fd(&self) -> BorrowedFd<'_> {
594            self.file.as_fd()
595        }
596    }
597    impl AsRawFd for Input {
598        fn as_raw_fd(&self) -> RawFd {
599            self.file.as_raw_fd()
600        }
601    }
602}
603
604// ---------------------------------------------------------------------------
605// Windows implementation
606// ---------------------------------------------------------------------------
607
608#[cfg(windows)]
609mod imp {
610    use std::io::{self, IoSlice, Read, Write};
611    use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
612    use std::ptr;
613    use std::sync::{Mutex, OnceLock};
614
615    use windows_sys::Win32::Foundation::HANDLE;
616    use windows_sys::Win32::Storage::FileSystem::{
617        FILE_TYPE_CHAR, GetFileType, ReadFile, WriteFile,
618    };
619    use windows_sys::Win32::System::Console::{
620        GetConsoleMode, GetStdHandle, ReadConsoleW, STD_ERROR_HANDLE, STD_INPUT_HANDLE,
621        STD_OUTPUT_HANDLE, WriteConsoleW,
622    };
623
624    // ---- Output ----------------------------------------------------------
625
626    pub(super) fn out() -> Output {
627        Output::new(STD_OUTPUT_HANDLE, &OUT_STATE)
628    }
629    pub(super) fn err() -> Output {
630        Output::new(STD_ERROR_HANDLE, &ERR_STATE)
631    }
632
633    static OUT_STATE: OnceLock<Mutex<PartialUtf8>> = OnceLock::new();
634    static ERR_STATE: OnceLock<Mutex<PartialUtf8>> = OnceLock::new();
635    static IN_STATE: OnceLock<Mutex<PartialUtf8>> = OnceLock::new();
636
637    pub(super) struct Output {
638        which: u32,
639        state: &'static OnceLock<Mutex<PartialUtf8>>,
640    }
641
642    impl Output {
643        fn new(which: u32, state: &'static OnceLock<Mutex<PartialUtf8>>) -> Self {
644            Self { which, state }
645        }
646        fn raw_handle(&self) -> HANDLE {
647            // SAFETY: GetStdHandle returns the inherited process handle
648            // or a null/invalid sentinel; callers downstream check for
649            // failure via WriteFile/WriteConsoleW returning 0.
650            unsafe { GetStdHandle(self.which) }
651        }
652        fn state(&self) -> &'static Mutex<PartialUtf8> {
653            self.state.get_or_init(|| Mutex::new(PartialUtf8::new()))
654        }
655    }
656
657    impl Write for Output {
658        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
659            let h = self.raw_handle();
660            if is_console(h) {
661                write_console(h, &mut self.state().lock().unwrap(), buf)
662            } else {
663                write_file(h, buf)
664            }
665        }
666        fn flush(&mut self) -> io::Result<()> {
667            Ok(())
668        }
669        fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
670            // No scatter syscall on Windows console writes; do the
671            // simple thing: write the first non-empty slice.
672            for b in bufs {
673                if !b.is_empty() {
674                    return self.write(b);
675                }
676            }
677            Ok(0)
678        }
679    }
680
681    impl AsHandle for Output {
682        fn as_handle(&self) -> BorrowedHandle<'_> {
683            // SAFETY: the returned BorrowedHandle is tied to `&self`
684            // and never outlives the process-inherited handle.
685            unsafe { BorrowedHandle::borrow_raw(self.raw_handle() as _) }
686        }
687    }
688    impl AsRawHandle for Output {
689        fn as_raw_handle(&self) -> RawHandle {
690            self.raw_handle() as RawHandle
691        }
692    }
693
694    // ---- Input -----------------------------------------------------------
695
696    pub(super) fn input() -> Input {
697        Input
698    }
699
700    pub(super) struct Input;
701
702    impl Input {
703        fn raw_handle(&self) -> HANDLE {
704            unsafe { GetStdHandle(STD_INPUT_HANDLE) }
705        }
706        fn state(&self) -> &'static Mutex<PartialUtf8> {
707            IN_STATE.get_or_init(|| Mutex::new(PartialUtf8::new()))
708        }
709    }
710
711    impl Read for Input {
712        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
713            let h = self.raw_handle();
714            if is_console(h) {
715                read_console(h, &mut self.state().lock().unwrap(), buf)
716            } else {
717                read_file(h, buf)
718            }
719        }
720    }
721
722    impl AsHandle for Input {
723        fn as_handle(&self) -> BorrowedHandle<'_> {
724            unsafe { BorrowedHandle::borrow_raw(self.raw_handle() as _) }
725        }
726    }
727    impl AsRawHandle for Input {
728        fn as_raw_handle(&self) -> RawHandle {
729            self.raw_handle() as RawHandle
730        }
731    }
732
733    // ---- shared helpers --------------------------------------------------
734
735    /// Carry-over for UTF-8 sequences split across calls.
736    ///
737    /// On the write side: holds the trailing bytes of a partial UTF-8
738    /// codepoint from the previous call so the next call can prepend
739    /// them.
740    ///
741    /// On the read side: holds the trailing bytes of a UTF-8 codepoint
742    /// that did not fit in the caller's buffer so the next call can
743    /// return them first. Sized at 8 bytes so up to two 4-byte
744    /// codepoints worth of overflow can be queued (see `read_console`
745    /// for the sizing argument).
746    struct PartialUtf8 {
747        buf: [u8; 8],
748        len: u8,
749    }
750
751    impl PartialUtf8 {
752        fn new() -> Self {
753            Self {
754                buf: [0; 8],
755                len: 0,
756            }
757        }
758    }
759
760    fn is_console(h: HANDLE) -> bool {
761        if h.is_null() || (h as isize) == -1 {
762            return false;
763        }
764        if unsafe { GetFileType(h) } != FILE_TYPE_CHAR {
765            return false;
766        }
767        let mut mode: u32 = 0;
768        unsafe { GetConsoleMode(h, &mut mode) != 0 }
769    }
770
771    fn write_file(h: HANDLE, buf: &[u8]) -> io::Result<usize> {
772        if buf.is_empty() {
773            return Ok(0);
774        }
775        // WriteFile takes a u32 length; cap to a safe chunk.
776        let len = buf.len().min(u32::MAX as usize) as u32;
777        let mut written: u32 = 0;
778        let ok = unsafe { WriteFile(h, buf.as_ptr(), len, &mut written, ptr::null_mut()) };
779        if ok == 0 {
780            return Err(io::Error::last_os_error());
781        }
782        Ok(written as usize)
783    }
784
785    fn read_file(h: HANDLE, buf: &mut [u8]) -> io::Result<usize> {
786        if buf.is_empty() {
787            return Ok(0);
788        }
789        let len = buf.len().min(u32::MAX as usize) as u32;
790        let mut read: u32 = 0;
791        let ok = unsafe { ReadFile(h, buf.as_mut_ptr(), len, &mut read, ptr::null_mut()) };
792        if ok == 0 {
793            return Err(io::Error::last_os_error());
794        }
795        Ok(read as usize)
796    }
797
798    /// Console write path.
799    ///
800    /// Validates `buf` (concatenated with any carried-over partial
801    /// UTF-8 prefix) as UTF-8, transcodes the longest valid prefix to
802    /// UTF-16, and writes it with `WriteConsoleW`. Saves any trailing
803    /// 1..=3 bytes of an incomplete final codepoint into `state` so a
804    /// future call can complete it.
805    ///
806    /// Returns the number of bytes from the *caller's* `buf` that were
807    /// consumed.
808    fn write_console(h: HANDLE, state: &mut PartialUtf8, buf: &[u8]) -> io::Result<usize> {
809        if buf.is_empty() {
810            return Ok(0);
811        }
812
813        // 1. Try to complete any saved partial sequence with the
814        //    leading bytes of `buf`. A UTF-8 codepoint is at most 4
815        //    bytes, so we need to append at most 3 leading bytes.
816        let mut leading_consumed: usize = 0;
817        if state.len > 0 {
818            while leading_consumed < buf.len() && (state.len as usize) < 4 {
819                state.buf[state.len as usize] = buf[leading_consumed];
820                state.len += 1;
821                leading_consumed += 1;
822
823                // After each append: if the saved bytes form a valid
824                // UTF-8 codepoint, emit it and clear the buffer.
825                if let Ok(s) = std::str::from_utf8(&state.buf[..state.len as usize]) {
826                    let c = s.chars().next().unwrap();
827                    let mut units = [0u16; 2];
828                    let encoded = c.encode_utf16(&mut units);
829                    write_utf16_console_all(h, encoded)?;
830                    state.len = 0;
831                    break;
832                }
833
834                // 4 bytes that still aren't valid UTF-8: bail out with
835                // a replacement character.
836                if state.len == 4 {
837                    write_utf16_console_all(h, &['\u{FFFD}' as u16])?;
838                    state.len = 0;
839                    break;
840                }
841            }
842            if state.len > 0 {
843                // Still incomplete; we've consumed everything we were
844                // given but produced no caller-visible codepoint yet.
845                return Ok(leading_consumed);
846            }
847        }
848
849        let rest = &buf[leading_consumed..];
850        if rest.is_empty() {
851            return Ok(leading_consumed);
852        }
853
854        // 2. Find the longest valid UTF-8 prefix of `rest`.
855        match std::str::from_utf8(rest) {
856            Ok(s) => {
857                write_utf8_to_console(h, s)?;
858                Ok(leading_consumed + rest.len())
859            }
860            Err(e) => {
861                let v = e.valid_up_to();
862                // SAFETY: `v` is a valid UTF-8 boundary as reported by
863                // `Utf8Error::valid_up_to`.
864                let valid = unsafe { std::str::from_utf8_unchecked(&rest[..v]) };
865                if !valid.is_empty() {
866                    write_utf8_to_console(h, valid)?;
867                }
868                match e.error_len() {
869                    Some(err_len) => {
870                        // Truly invalid bytes; emit a single
871                        // replacement and report progress past them.
872                        write_utf16_console_all(h, &['\u{FFFD}' as u16])?;
873                        Ok(leading_consumed + v + err_len)
874                    }
875                    None => {
876                        // Trailing partial codepoint; stash 1..=3
877                        // bytes for the next call to complete.
878                        let trailing = &rest[v..];
879                        state.buf[..trailing.len()].copy_from_slice(trailing);
880                        state.len = trailing.len() as u8;
881                        Ok(leading_consumed + rest.len())
882                    }
883                }
884            }
885        }
886    }
887
888    /// Transcode `s` to UTF-16 and write it with `WriteConsoleW`,
889    /// looping until every code unit is delivered.
890    fn write_utf8_to_console(h: HANDLE, s: &str) -> io::Result<()> {
891        // Use a small stack buffer and refill it in chunks to avoid an
892        // unbounded heap allocation for very large writes.
893        const CHUNK: usize = 1024;
894        let mut buf = [0u16; CHUNK];
895        let mut idx = 0;
896        for c in s.chars() {
897            let need = c.len_utf16();
898            if idx + need > CHUNK {
899                write_utf16_console_all(h, &buf[..idx])?;
900                idx = 0;
901            }
902            // Encode directly into the buffer.
903            let written = c.encode_utf16(&mut buf[idx..idx + need]).len();
904            idx += written;
905        }
906        if idx > 0 {
907            write_utf16_console_all(h, &buf[..idx])?;
908        }
909        Ok(())
910    }
911
912    /// Single `WriteConsoleW` call; returns the number of UTF-16 code
913    /// units written, or an error.
914    fn write_utf16_console(h: HANDLE, data: &[u16]) -> io::Result<usize> {
915        if data.is_empty() {
916            return Ok(0);
917        }
918        let mut written: u32 = 0;
919        let ok = unsafe {
920            WriteConsoleW(
921                h,
922                data.as_ptr(),
923                data.len() as u32,
924                &mut written,
925                ptr::null(),
926            )
927        };
928        if ok == 0 {
929            return Err(io::Error::last_os_error());
930        }
931        Ok(written as usize)
932    }
933
934    /// `WriteConsoleW` looped until the entire slice is consumed.
935    fn write_utf16_console_all(h: HANDLE, mut data: &[u16]) -> io::Result<()> {
936        while !data.is_empty() {
937            let n = write_utf16_console(h, data)?;
938            if n == 0 {
939                return Err(io::Error::new(
940                    io::ErrorKind::WriteZero,
941                    "WriteConsoleW returned 0 with non-empty buffer",
942                ));
943            }
944            data = &data[n..];
945        }
946        Ok(())
947    }
948
949    /// Console read path.
950    ///
951    /// Drains any UTF-8 bytes previously stashed (overflow from the
952    /// last read), then calls `ReadConsoleW` for a fresh batch and
953    /// transcodes UTF-16 → UTF-8 into the caller's buffer. If the
954    /// caller's buffer is too small for a decoded codepoint, the
955    /// remaining bytes are saved in `state` for the next call.
956    ///
957    /// Sizing: each UTF-16 code unit decodes to at most 3 UTF-8 bytes
958    /// (BMP), and a surrogate pair (2 units) decodes to 4 UTF-8 bytes
959    /// total. We cap the request at `(buf.len() + 4) / 3` units, with
960    /// a floor of 2 so a surrogate pair can be paired in a single
961    /// read. That bounds the produced UTF-8 byte count at
962    /// `request * 3`, which never exceeds `buf.len() + stash_cap`
963    /// (`stash_cap == 8`) for `buf.len() >= 1`.
964    fn read_console(h: HANDLE, state: &mut PartialUtf8, buf: &mut [u8]) -> io::Result<usize> {
965        if buf.is_empty() {
966            return Ok(0);
967        }
968
969        // 1. Drain anything we stashed from a previous call.
970        if state.len > 0 {
971            let n = (state.len as usize).min(buf.len());
972            buf[..n].copy_from_slice(&state.buf[..n]);
973            let rem = state.len as usize - n;
974            if rem > 0 {
975                state.buf.copy_within(n..n + rem, 0);
976            }
977            state.len = rem as u8;
978            return Ok(n);
979        }
980
981        // 2. Read a bounded number of UTF-16 code units so that the
982        //    decoded UTF-8 byte count fits in `buf` plus the stash.
983        const MAX_UNITS: usize = 256;
984        let want = ((buf.len() + 4) / 3).clamp(2, MAX_UNITS);
985        let mut units = [0u16; MAX_UNITS];
986        let mut read_units: u32 = 0;
987        let ok = unsafe {
988            ReadConsoleW(
989                h,
990                units.as_mut_ptr().cast(),
991                want as u32,
992                &mut read_units,
993                ptr::null(),
994            )
995        };
996        if ok == 0 {
997            return Err(io::Error::last_os_error());
998        }
999        if read_units == 0 {
1000            return Ok(0);
1001        }
1002        let read_units = read_units as usize;
1003
1004        // 3. Decode all received UTF-16 units to UTF-8 in one pass into
1005        //    a stack scratch buffer; then split between caller `buf`
1006        //    and `state.buf`.
1007        //
1008        // Worst case decoded size: `read_units * 3` for an all-BMP
1009        // 3-byte-UTF-8 stream. `read_units <= MAX_UNITS == 256`, so
1010        // 768 bytes suffices.
1011        let mut scratch = [0u8; MAX_UNITS * 3];
1012        let mut s_len = 0usize;
1013        for r in char::decode_utf16(units[..read_units].iter().copied()) {
1014            let c = r.unwrap_or('\u{FFFD}');
1015            let n = c.len_utf8();
1016            c.encode_utf8(&mut scratch[s_len..s_len + n]);
1017            s_len += n;
1018        }
1019
1020        let n = s_len.min(buf.len());
1021        buf[..n].copy_from_slice(&scratch[..n]);
1022        let overflow = s_len - n;
1023        if overflow > 0 {
1024            debug_assert!(overflow <= state.buf.len());
1025            let cap = state.buf.len();
1026            let take = overflow.min(cap);
1027            state.buf[..take].copy_from_slice(&scratch[n..n + take]);
1028            state.len = take as u8;
1029        }
1030        Ok(n)
1031    }
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036    use super::*;
1037    use std::io::Write;
1038
1039    #[test]
1040    fn stdout_handle_constructible() {
1041        let _ = stdout();
1042        let _ = stderr();
1043        let _ = stdin();
1044    }
1045
1046    #[test]
1047    fn stdout_write_empty_ok() {
1048        // Writing zero bytes must be a no-op regardless of attachment.
1049        let mut out = stdout();
1050        assert!(matches!(out.write(b""), Ok(0)));
1051    }
1052
1053    #[cfg(unix)]
1054    #[test]
1055    fn handles_expose_inherited_fds() {
1056        use std::os::fd::AsRawFd;
1057        assert_eq!(stdin().as_raw_fd(), libc::STDIN_FILENO);
1058        assert_eq!(stdout().as_raw_fd(), libc::STDOUT_FILENO);
1059        assert_eq!(stderr().as_raw_fd(), libc::STDERR_FILENO);
1060    }
1061
1062    #[cfg(windows)]
1063    #[test]
1064    fn handles_match_getstdhandle() {
1065        use std::os::windows::io::AsRawHandle;
1066        use windows_sys::Win32::System::Console::{
1067            GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
1068        };
1069        unsafe {
1070            assert_eq!(
1071                stdin().as_raw_handle() as isize,
1072                GetStdHandle(STD_INPUT_HANDLE) as isize
1073            );
1074            assert_eq!(
1075                stdout().as_raw_handle() as isize,
1076                GetStdHandle(STD_OUTPUT_HANDLE) as isize
1077            );
1078            assert_eq!(
1079                stderr().as_raw_handle() as isize,
1080                GetStdHandle(STD_ERROR_HANDLE) as isize
1081            );
1082        }
1083    }
1084}