mirror of
https://github.com/wez/wezterm.git
synced 2024-11-24 07:46:59 +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 failure::Error;
|
||||||
use std::process::ExitStatus;
|
|
||||||
|
|
||||||
#[cfg(any(windows, feature = "force-glutin", target_os = "macos"))]
|
#[cfg(any(windows, feature = "force-glutin", target_os = "macos"))]
|
||||||
mod glutinloop;
|
mod glutinloop;
|
||||||
|
@ -70,11 +70,11 @@ use font::FontConfiguration;
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod pty;
|
mod pty;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub use pty::{openpty, Child, Command, MasterPty, SlavePty};
|
pub use pty::{openpty, Child, Command, ExitStatus, MasterPty, SlavePty};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod winpty;
|
mod winpty;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub use winpty::{openpty, Child, Command, MasterPty, SlavePty};
|
pub use winpty::{openpty, Child, Command, ExitStatus, MasterPty, SlavePty};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod sigchld;
|
mod sigchld;
|
||||||
|
|
||||||
|
326
src/winpty.rs
326
src/winpty.rs
@ -1,7 +1,12 @@
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
use std::io;
|
use std::io::{self, Error as IoError, Result as IoResult};
|
||||||
use std::io::Error as IoError;
|
|
||||||
extern crate winapi;
|
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::os::windows::raw::HANDLE;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::{Arc, Mutex};
|
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::fileapi::{ReadFile, WriteFile};
|
||||||
use winpty::winapi::um::handleapi::*;
|
use winpty::winapi::um::handleapi::*;
|
||||||
use winpty::winapi::um::namedpipeapi::CreatePipe;
|
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;
|
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;
|
type HPCON = HANDLE;
|
||||||
|
|
||||||
@ -62,6 +347,7 @@ impl PsuedoCon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct OwnedHandle {
|
struct OwnedHandle {
|
||||||
handle: HANDLE,
|
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 {
|
struct Inner {
|
||||||
con: PsuedoCon,
|
con: PsuedoCon,
|
||||||
readable: OwnedHandle,
|
readable: OwnedHandle,
|
||||||
@ -192,7 +503,14 @@ impl io::Read for MasterPty {
|
|||||||
|
|
||||||
impl SlavePty {
|
impl SlavePty {
|
||||||
pub fn spawn_command(self, mut cmd: Command) -> Result<Child, Error> {
|
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