use std::ffi::OsStr; use std::io::{self, Error, ErrorKind, Result}; use std::iter::once; use std::os::windows::ffi::OsStrExt; use std::sync::mpsc::TryRecvError; use std::sync::Arc; use crate::event::{OnResize, WindowSize}; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite, Options, Shell}; mod blocking; mod child; mod conpty; use blocking::{UnblockedReader, UnblockedWriter}; use conpty::Conpty as Backend; use miow::pipe::{AnonRead, AnonWrite}; use polling::{Event, Poller}; pub const PTY_CHILD_EVENT_TOKEN: usize = 1; pub const PTY_READ_WRITE_TOKEN: usize = 2; type ReadPipe = UnblockedReader; type WritePipe = UnblockedWriter; pub struct Pty { // XXX: Backend is required to be the first field, to ensure correct drop order. Dropping // `conout` before `backend` will cause a deadlock (with Conpty). backend: Backend, conout: ReadPipe, conin: WritePipe, child_watcher: ChildExitWatcher, } pub fn new(config: &Options, window_size: WindowSize, _window_id: u64) -> Result { conpty::new(config, window_size) .ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty")) } impl Pty { fn new( backend: impl Into, conout: impl Into, conin: impl Into, child_watcher: ChildExitWatcher, ) -> Self { Self { backend: backend.into(), conout: conout.into(), conin: conin.into(), child_watcher } } pub fn child_watcher(&self) -> &ChildExitWatcher { &self.child_watcher } } fn with_key(mut event: Event, key: usize) -> Event { event.key = key; event } impl EventedReadWrite for Pty { type Reader = ReadPipe; type Writer = WritePipe; #[inline] unsafe fn register( &mut self, poll: &Arc, interest: polling::Event, poll_opts: polling::PollMode, ) -> io::Result<()> { self.conin.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); self.conout.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); self.child_watcher.register(poll, with_key(interest, PTY_CHILD_EVENT_TOKEN)); Ok(()) } #[inline] fn reregister( &mut self, poll: &Arc, interest: polling::Event, poll_opts: polling::PollMode, ) -> io::Result<()> { self.conin.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); self.conout.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); self.child_watcher.register(poll, with_key(interest, PTY_CHILD_EVENT_TOKEN)); Ok(()) } #[inline] fn deregister(&mut self, _poll: &Arc) -> io::Result<()> { self.conin.deregister(); self.conout.deregister(); self.child_watcher.deregister(); Ok(()) } #[inline] fn reader(&mut self) -> &mut Self::Reader { &mut self.conout } #[inline] fn writer(&mut self) -> &mut Self::Writer { &mut self.conin } } impl EventedPty for Pty { fn next_child_event(&mut self) -> Option { match self.child_watcher.event_rx().try_recv() { Ok(ev) => Some(ev), Err(TryRecvError::Empty) => None, Err(TryRecvError::Disconnected) => Some(ChildEvent::Exited(None)), } } } impl OnResize for Pty { fn on_resize(&mut self, window_size: WindowSize) { self.backend.on_resize(window_size) } } fn cmdline(config: &Options) -> String { let default_shell = Shell::new("powershell".to_owned(), Vec::new()); let shell = config.shell.as_ref().unwrap_or(&default_shell); once(shell.program.as_str()) .chain(shell.args.iter().map(|s| s.as_str())) .collect::>() .join(" ") } /// Converts the string slice into a Windows-standard representation for "W"- /// suffixed function variants, which accept UTF-16 encoded string values. pub fn win32_string + ?Sized>(value: &S) -> Vec { OsStr::new(value).encode_wide().chain(once(0)).collect() }