mirror of
https://github.com/wez/wezterm.git
synced 2024-12-26 23:04:49 +03:00
implement pty enabled spawner for win32
We can now start a process with a new pty, but fail when we reach the shader spec. Progress!
This commit is contained in:
parent
c650cbbf08
commit
b387464054
@ -1,5 +1,5 @@
|
||||
use super::ExitStatus;
|
||||
use failure::Error;
|
||||
use std::process::ExitStatus;
|
||||
|
||||
#[cfg(any(windows, feature = "force-glutin", target_os = "macos"))]
|
||||
mod glutinloop;
|
||||
|
@ -70,11 +70,11 @@ use font::FontConfiguration;
|
||||
#[cfg(unix)]
|
||||
mod pty;
|
||||
#[cfg(unix)]
|
||||
pub use pty::{openpty, Child, Command, MasterPty, SlavePty};
|
||||
pub use pty::{openpty, Child, Command, ExitStatus, MasterPty, SlavePty};
|
||||
#[cfg(windows)]
|
||||
mod winpty;
|
||||
#[cfg(windows)]
|
||||
pub use winpty::{openpty, Child, Command, MasterPty, SlavePty};
|
||||
pub use winpty::{openpty, Child, Command, ExitStatus, MasterPty, SlavePty};
|
||||
#[cfg(unix)]
|
||||
mod sigchld;
|
||||
|
||||
|
326
src/winpty.rs
326
src/winpty.rs
@ -1,7 +1,12 @@
|
||||
use failure::Error;
|
||||
use std::io;
|
||||
use std::io::Error as IoError;
|
||||
use std::io::{self, Error as IoError, Result as IoResult};
|
||||
extern crate winapi;
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs;
|
||||
use std::mem;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::os::windows::raw::HANDLE;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -10,9 +15,289 @@ use winpty::winapi::shared::winerror::{HRESULT, S_OK};
|
||||
use winpty::winapi::um::fileapi::{ReadFile, WriteFile};
|
||||
use winpty::winapi::um::handleapi::*;
|
||||
use winpty::winapi::um::namedpipeapi::CreatePipe;
|
||||
use winpty::winapi::um::processthreadsapi::*;
|
||||
use winpty::winapi::um::winbase::EXTENDED_STARTUPINFO_PRESENT;
|
||||
use winpty::winapi::um::winbase::STARTUPINFOEXW;
|
||||
use winpty::winapi::um::wincon::COORD;
|
||||
|
||||
pub use std::process::{Child, Command};
|
||||
const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: usize = 0x00020016;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Command {
|
||||
args: Vec<OsString>,
|
||||
input: Option<OwnedHandle>,
|
||||
output: Option<OwnedHandle>,
|
||||
hpc: Option<HPCON>,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
|
||||
Self {
|
||||
args: vec![Self::search_path(program.as_ref().to_owned())],
|
||||
input: None,
|
||||
output: None,
|
||||
hpc: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn search_path(exe: OsString) -> OsString {
|
||||
if let Some(path) = env::var_os("PATH") {
|
||||
let extensions = env::var_os("PATHEXT").unwrap_or(".EXE".into());
|
||||
for path in env::split_paths(&path) {
|
||||
// Check for exactly the user's string in this path dir
|
||||
let candidate = path.join(&exe);
|
||||
if fs::metadata(&candidate).is_ok() {
|
||||
return candidate.into_os_string();
|
||||
}
|
||||
|
||||
// otherwise try tacking on some extensions.
|
||||
// Note that this really replaces the extension in the
|
||||
// user specified path, so this is potentially wrong.
|
||||
for ext in env::split_paths(&extensions) {
|
||||
// PATHEXT includes the leading `.`, but `with_extension`
|
||||
// doesn't want that
|
||||
let ext = ext.to_str().expect("PATHEXT entries must be utf8");
|
||||
let path = path.join(&exe).with_extension(&ext[1..]);
|
||||
if fs::metadata(&path).is_ok() {
|
||||
return path.into_os_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exe
|
||||
}
|
||||
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
|
||||
// FIXME: quoting!
|
||||
self.args.push(arg.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn args<I, S>(&mut self, args: I) -> &mut Command
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
for arg in args {
|
||||
self.arg(arg);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
eprintln!(
|
||||
"ignoring env {:?}={:?} for child; FIXME: implement this!",
|
||||
key.as_ref(),
|
||||
val.as_ref()
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
fn set_pty(&mut self, input: OwnedHandle, output: OwnedHandle, con: HPCON) -> &mut Command {
|
||||
self.input.replace(input);
|
||||
self.output.replace(output);
|
||||
self.hpc.replace(con);
|
||||
self
|
||||
}
|
||||
|
||||
fn cmdline(&self) -> Result<Vec<u16>, Error> {
|
||||
let mut cmdline = Vec::<u16>::new();
|
||||
for (idx, arg) in self.args.iter().enumerate() {
|
||||
if idx != 0 {
|
||||
cmdline.push(' ' as u16);
|
||||
}
|
||||
ensure!(
|
||||
!arg.encode_wide().any(|c| c == 0),
|
||||
"invalid encoding for command line argument at index {}: {:?}",
|
||||
idx,
|
||||
arg
|
||||
);
|
||||
Self::append_quoted(arg, &mut cmdline);
|
||||
}
|
||||
Ok(cmdline)
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/hniksic/rust-subprocess/blob/873dfed165173e52907beb87118b2c0c05d8b8a1/src/popen.rs#L1117
|
||||
// which in turn was translated from ArgvQuote at http://tinyurl.com/zmgtnls
|
||||
fn append_quoted(arg: &OsStr, cmdline: &mut Vec<u16>) {
|
||||
if !arg.is_empty()
|
||||
&& !arg.encode_wide().any(|c| {
|
||||
c == ' ' as u16
|
||||
|| c == '\t' as u16
|
||||
|| c == '\n' as u16
|
||||
|| c == '\x0b' as u16
|
||||
|| c == '\"' as u16
|
||||
})
|
||||
{
|
||||
cmdline.extend(arg.encode_wide());
|
||||
return;
|
||||
}
|
||||
cmdline.push('"' as u16);
|
||||
|
||||
let arg: Vec<_> = arg.encode_wide().collect();
|
||||
let mut i = 0;
|
||||
while i < arg.len() {
|
||||
let mut num_backslashes = 0;
|
||||
while i < arg.len() && arg[i] == '\\' as u16 {
|
||||
i += 1;
|
||||
num_backslashes += 1;
|
||||
}
|
||||
|
||||
if i == arg.len() {
|
||||
for _ in 0..num_backslashes * 2 {
|
||||
cmdline.push('\\' as u16);
|
||||
}
|
||||
break;
|
||||
} else if arg[i] == b'"' as u16 {
|
||||
for _ in 0..num_backslashes * 2 + 1 {
|
||||
cmdline.push('\\' as u16);
|
||||
}
|
||||
cmdline.push(arg[i]);
|
||||
} else {
|
||||
for _ in 0..num_backslashes {
|
||||
cmdline.push('\\' as u16);
|
||||
}
|
||||
cmdline.push(arg[i]);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
cmdline.push('"' as u16);
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) -> Result<Child, Error> {
|
||||
let mut si: STARTUPINFOEXW = unsafe { mem::zeroed() };
|
||||
si.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
|
||||
|
||||
let mut attrs = ProcThreadAttributeList::with_capacity(1)?;
|
||||
attrs.set_pty(*self.hpc.as_ref().unwrap())?;
|
||||
si.lpAttributeList = attrs.as_mut_ptr();
|
||||
|
||||
let mut pi: PROCESS_INFORMATION = unsafe { mem::zeroed() };
|
||||
|
||||
let mut cmdline = self.cmdline()?;
|
||||
let res = unsafe {
|
||||
CreateProcessW(
|
||||
ptr::null(),
|
||||
cmdline.as_mut_slice().as_mut_ptr(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
EXTENDED_STARTUPINFO_PRESENT,
|
||||
ptr::null_mut(), // FIXME: env
|
||||
ptr::null_mut(),
|
||||
&mut si.StartupInfo,
|
||||
&mut pi,
|
||||
)
|
||||
};
|
||||
if res == 0 {
|
||||
bail!(
|
||||
"CreateProcessW `{:?}` failed: {}",
|
||||
OsString::from_wide(&cmdline),
|
||||
IoError::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure we close out the thread handle so we don't leak it;
|
||||
// we do this simply by making it owned
|
||||
let _main_thread = OwnedHandle { handle: pi.hThread };
|
||||
let proc = OwnedHandle {
|
||||
handle: pi.hProcess,
|
||||
};
|
||||
|
||||
Ok(Child { proc })
|
||||
}
|
||||
}
|
||||
|
||||
struct ProcThreadAttributeList {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ProcThreadAttributeList {
|
||||
pub fn with_capacity(num_attributes: DWORD) -> Result<Self, Error> {
|
||||
let mut bytes_required: usize = 0;
|
||||
unsafe {
|
||||
InitializeProcThreadAttributeList(
|
||||
ptr::null_mut(),
|
||||
num_attributes,
|
||||
0,
|
||||
&mut bytes_required,
|
||||
)
|
||||
};
|
||||
let mut data = Vec::with_capacity(bytes_required);
|
||||
// We have the right capacity, so force the vec to consider itself
|
||||
// that length. The contents of those bytes will be maintained
|
||||
// by the win32 apis used in this impl.
|
||||
unsafe { data.set_len(bytes_required) };
|
||||
|
||||
let attr_ptr = data.as_mut_slice().as_mut_ptr() as *mut _;
|
||||
let res = unsafe {
|
||||
InitializeProcThreadAttributeList(attr_ptr, num_attributes, 0, &mut bytes_required)
|
||||
};
|
||||
ensure!(
|
||||
res != 0,
|
||||
"InitializeProcThreadAttributeList failed: {}",
|
||||
IoError::last_os_error()
|
||||
);
|
||||
Ok(Self { data })
|
||||
}
|
||||
|
||||
pub fn as_mut_ptr(&mut self) -> LPPROC_THREAD_ATTRIBUTE_LIST {
|
||||
self.data.as_mut_slice().as_mut_ptr() as *mut _
|
||||
}
|
||||
|
||||
pub fn set_pty(&mut self, con: HPCON) -> Result<(), Error> {
|
||||
let res = unsafe {
|
||||
UpdateProcThreadAttribute(
|
||||
self.as_mut_ptr(),
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
con,
|
||||
mem::size_of::<HPCON>(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
ensure!(
|
||||
res != 0,
|
||||
"UpdateProcThreadAttribute failed: {}",
|
||||
IoError::last_os_error()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ProcThreadAttributeList {
|
||||
fn drop(&mut self) {
|
||||
unsafe { DeleteProcThreadAttributeList(self.as_mut_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Child {
|
||||
proc: OwnedHandle,
|
||||
}
|
||||
|
||||
impl Child {
|
||||
pub fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
|
||||
let mut status: DWORD = 0;
|
||||
let res = unsafe { GetExitCodeProcess(self.proc.handle, &mut status) };
|
||||
if res != 0 {
|
||||
Ok(Some(ExitStatus { status }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExitStatus {
|
||||
status: DWORD,
|
||||
}
|
||||
|
||||
type HPCON = HANDLE;
|
||||
|
||||
@ -62,6 +347,7 @@ impl PsuedoCon {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OwnedHandle {
|
||||
handle: HANDLE,
|
||||
}
|
||||
@ -72,6 +358,31 @@ impl Drop for OwnedHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedHandle {
|
||||
fn try_clone(&self) -> Result<Self, IoError> {
|
||||
let proc = unsafe { GetCurrentProcess() };
|
||||
let mut duped = INVALID_HANDLE_VALUE;
|
||||
let ok = unsafe {
|
||||
DuplicateHandle(
|
||||
proc,
|
||||
self.handle as *mut _,
|
||||
proc,
|
||||
&mut duped,
|
||||
0,
|
||||
0,
|
||||
winapi::um::winnt::DUPLICATE_SAME_ACCESS,
|
||||
)
|
||||
};
|
||||
if ok == 0 {
|
||||
Err(IoError::last_os_error())
|
||||
} else {
|
||||
Ok(OwnedHandle {
|
||||
handle: duped as *mut _,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
con: PsuedoCon,
|
||||
readable: OwnedHandle,
|
||||
@ -192,7 +503,14 @@ impl io::Read for MasterPty {
|
||||
|
||||
impl SlavePty {
|
||||
pub fn spawn_command(self, mut cmd: Command) -> Result<Child, Error> {
|
||||
bail!("spawn_command not implemented")
|
||||
let inner = self.inner.lock().unwrap();
|
||||
cmd.set_pty(
|
||||
inner.writable.try_clone()?,
|
||||
inner.readable.try_clone()?,
|
||||
inner.con.con,
|
||||
);
|
||||
|
||||
cmd.spawn()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user