use std::ffi::OsString;
use std::io::{self, Read, Write};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PtySize {
pub rows: u16,
pub cols: u16,
pub pixel_width: u16,
pub pixel_height: u16,
}
pub trait PtyMaster: Send + 'static {
fn try_clone_reader(&mut self) -> io::Result<Box<dyn Read + Send>>;
fn take_writer(&mut self) -> io::Result<Box<dyn Write + Send>>;
fn resize(&self, size: PtySize) -> io::Result<()>;
fn get_size(&self) -> io::Result<PtySize>;
#[cfg(unix)]
fn process_group_leader(&self) -> Option<i32>;
}
pub trait PtyChild: Send + 'static {
fn pid(&self) -> u32;
fn try_wait(&mut self) -> io::Result<Option<u32>>;
fn wait(&mut self) -> io::Result<u32>;
fn kill(&mut self) -> io::Result<()>;
#[cfg(windows)]
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle>;
}
pub trait PtySlave: Send + 'static {
type Child: PtyChild;
fn spawn(
self,
argv: &[OsString],
cwd: Option<&Path>,
env: Option<&[(OsString, OsString)]>,
) -> io::Result<Self::Child>;
}
pub trait PtyBackend {
type Master: PtyMaster;
type Slave: PtySlave;
fn openpty(size: PtySize) -> io::Result<(Self::Master, Self::Slave)>;
}
#[cfg(windows)]
mod conpty {
use super::*;
use crate::pty::conpty_passthrough;
pub(crate) struct ConPtyBackend;
impl PtyBackend for ConPtyBackend {
type Master = conpty_passthrough::ConPtyMaster;
type Slave = conpty_passthrough::ConPtySlave;
fn openpty(size: PtySize) -> io::Result<(Self::Master, Self::Slave)> {
let pair = conpty_passthrough::openpty(conpty_passthrough::PtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})?;
Ok((pair.master, pair.slave))
}
}
impl PtyMaster for conpty_passthrough::ConPtyMaster {
fn try_clone_reader(&mut self) -> io::Result<Box<dyn Read + Send>> {
conpty_passthrough::ConPtyMaster::try_clone_reader(self)
}
fn take_writer(&mut self) -> io::Result<Box<dyn Write + Send>> {
conpty_passthrough::ConPtyMaster::take_writer(self)
}
fn resize(&self, size: PtySize) -> io::Result<()> {
conpty_passthrough::ConPtyMaster::resize(
self,
conpty_passthrough::PtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
},
)
}
fn get_size(&self) -> io::Result<PtySize> {
let s = conpty_passthrough::ConPtyMaster::get_size(self);
Ok(PtySize {
rows: s.rows,
cols: s.cols,
pixel_width: s.pixel_width,
pixel_height: s.pixel_height,
})
}
}
impl PtySlave for conpty_passthrough::ConPtySlave {
type Child = conpty_passthrough::child::ConPtyChild;
fn spawn(
self,
argv: &[OsString],
cwd: Option<&Path>,
env: Option<&[(OsString, OsString)]>,
) -> io::Result<Self::Child> {
conpty_passthrough::ConPtySlave::spawn(self, argv, cwd, env)
}
}
impl PtyChild for conpty_passthrough::child::ConPtyChild {
fn pid(&self) -> u32 {
conpty_passthrough::child::ConPtyChild::pid(self)
}
fn try_wait(&mut self) -> io::Result<Option<u32>> {
conpty_passthrough::child::ConPtyChild::try_wait(self)
}
fn wait(&mut self) -> io::Result<u32> {
conpty_passthrough::child::ConPtyChild::wait(self)
}
fn kill(&mut self) -> io::Result<()> {
conpty_passthrough::child::ConPtyChild::kill(self)
}
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
Some(conpty_passthrough::child::ConPtyChild::as_raw_handle(self))
}
}
}
#[cfg(unix)]
mod unix {
use super::*;
use portable_pty::{
native_pty_system, Child as PortableChild, CommandBuilder, MasterPty,
PtySize as PortPtySize, SlavePty,
};
pub(crate) struct PortablePtyBackend;
pub(crate) struct PortablePtyMaster(Box<dyn MasterPty + Send>);
pub(crate) struct PortablePtySlave(Box<dyn SlavePty + Send>);
pub(crate) struct PortablePtyChild(Box<dyn PortableChild + Send + Sync>);
impl PtyBackend for PortablePtyBackend {
type Master = PortablePtyMaster;
type Slave = PortablePtySlave;
fn openpty(size: PtySize) -> io::Result<(Self::Master, Self::Slave)> {
let sys = native_pty_system();
let pair = sys
.openpty(PortPtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})
.map_err(io::Error::other)?;
Ok((PortablePtyMaster(pair.master), PortablePtySlave(pair.slave)))
}
}
impl PtyMaster for PortablePtyMaster {
fn try_clone_reader(&mut self) -> io::Result<Box<dyn Read + Send>> {
self.0.try_clone_reader().map_err(io::Error::other)
}
fn take_writer(&mut self) -> io::Result<Box<dyn Write + Send>> {
self.0.take_writer().map_err(io::Error::other)
}
fn resize(&self, size: PtySize) -> io::Result<()> {
self.0
.resize(PortPtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})
.map_err(io::Error::other)
}
fn get_size(&self) -> io::Result<PtySize> {
let s = self.0.get_size().map_err(io::Error::other)?;
Ok(PtySize {
rows: s.rows,
cols: s.cols,
pixel_width: s.pixel_width,
pixel_height: s.pixel_height,
})
}
fn process_group_leader(&self) -> Option<i32> {
self.0.process_group_leader()
}
}
impl PtySlave for PortablePtySlave {
type Child = PortablePtyChild;
fn spawn(
self,
argv: &[OsString],
cwd: Option<&Path>,
env: Option<&[(OsString, OsString)]>,
) -> io::Result<Self::Child> {
if argv.is_empty() {
return Err(io::Error::other(
"portable-pty spawn requires non-empty argv",
));
}
let mut cmd = CommandBuilder::new(&argv[0]);
for arg in &argv[1..] {
cmd.arg(arg);
}
if let Some(cwd) = cwd {
cmd.cwd(cwd);
}
if let Some(env) = env {
cmd.env_clear();
for (k, v) in env {
cmd.env(k, v);
}
}
let child = self.0.spawn_command(cmd).map_err(io::Error::other)?;
Ok(PortablePtyChild(child))
}
}
impl PtyChild for PortablePtyChild {
fn pid(&self) -> u32 {
self.0.process_id().unwrap_or(0)
}
fn try_wait(&mut self) -> io::Result<Option<u32>> {
match self.0.try_wait()? {
Some(status) => Ok(Some(portable_pty_exit_code(status))),
None => Ok(None),
}
}
fn wait(&mut self) -> io::Result<u32> {
let status = self.0.wait()?;
Ok(portable_pty_exit_code(status))
}
fn kill(&mut self) -> io::Result<()> {
self.0.kill()
}
}
fn portable_pty_exit_code(status: portable_pty::ExitStatus) -> u32 {
status.exit_code()
}
}
#[cfg(windows)]
pub(crate) type Backend = conpty::ConPtyBackend;
#[cfg(unix)]
pub(crate) type Backend = unix::PortablePtyBackend;
#[cfg(windows)]
#[allow(dead_code)]
mod unix_compat {
use super::*;
use portable_pty::{
native_pty_system, Child as PortableChild, CommandBuilder, MasterPty,
PtySize as PortPtySize, SlavePty,
};
pub(crate) struct PortablePtyBackend;
pub(crate) struct PortablePtyMaster(Box<dyn MasterPty + Send>);
pub(crate) struct PortablePtySlave(Box<dyn SlavePty + Send>);
pub(crate) struct PortablePtyChild(Box<dyn PortableChild + Send + Sync>);
impl PtyBackend for PortablePtyBackend {
type Master = PortablePtyMaster;
type Slave = PortablePtySlave;
fn openpty(size: PtySize) -> io::Result<(Self::Master, Self::Slave)> {
let sys = native_pty_system();
let pair = sys
.openpty(PortPtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})
.map_err(io::Error::other)?;
Ok((PortablePtyMaster(pair.master), PortablePtySlave(pair.slave)))
}
}
impl PtyMaster for PortablePtyMaster {
fn try_clone_reader(&mut self) -> io::Result<Box<dyn Read + Send>> {
self.0.try_clone_reader().map_err(io::Error::other)
}
fn take_writer(&mut self) -> io::Result<Box<dyn Write + Send>> {
self.0.take_writer().map_err(io::Error::other)
}
fn resize(&self, size: PtySize) -> io::Result<()> {
self.0
.resize(PortPtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})
.map_err(io::Error::other)
}
fn get_size(&self) -> io::Result<PtySize> {
let s = self.0.get_size().map_err(io::Error::other)?;
Ok(PtySize {
rows: s.rows,
cols: s.cols,
pixel_width: s.pixel_width,
pixel_height: s.pixel_height,
})
}
}
impl PtySlave for PortablePtySlave {
type Child = PortablePtyChild;
fn spawn(
self,
argv: &[OsString],
cwd: Option<&Path>,
env: Option<&[(OsString, OsString)]>,
) -> io::Result<Self::Child> {
if argv.is_empty() {
return Err(io::Error::other(
"portable-pty spawn requires non-empty argv",
));
}
let mut cmd = CommandBuilder::new(&argv[0]);
for arg in &argv[1..] {
cmd.arg(arg);
}
if let Some(cwd) = cwd {
cmd.cwd(cwd);
}
if let Some(env) = env {
cmd.env_clear();
for (k, v) in env {
cmd.env(k, v);
}
}
let child = self.0.spawn_command(cmd).map_err(io::Error::other)?;
Ok(PortablePtyChild(child))
}
}
impl PtyChild for PortablePtyChild {
fn pid(&self) -> u32 {
self.0.process_id().unwrap_or(0)
}
fn try_wait(&mut self) -> io::Result<Option<u32>> {
match self.0.try_wait()? {
Some(status) => Ok(Some(status.exit_code())),
None => Ok(None),
}
}
fn wait(&mut self) -> io::Result<u32> {
let status = self.0.wait()?;
Ok(status.exit_code())
}
fn kill(&mut self) -> io::Result<()> {
self.0.kill()
}
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
self.0.as_raw_handle()
}
}
}