Vendor ConPtySystem (#7656)

The repo we were depending on is very large and we need very small part
of it.

---------

Co-authored-by: Pavel <pavel@krymets.com>
This commit is contained in:
pakrym-oai
2025-12-09 09:23:51 -08:00
committed by GitHub
parent 2237b701b6
commit 164265bed1
10 changed files with 789 additions and 5 deletions

View File

@@ -1 +1,2 @@
iTerm
psuedo

13
codex-rs/Cargo.lock generated
View File

@@ -1673,8 +1673,13 @@ name = "codex-utils-pty"
version = "0.0.0"
dependencies = [
"anyhow",
"filedescriptor",
"lazy_static",
"log",
"portable-pty",
"shared_library",
"tokio",
"winapi",
]
[[package]]
@@ -2578,7 +2583,8 @@ dependencies = [
[[package]]
name = "filedescriptor"
version = "0.8.3"
source = "git+https://github.com/pakrym/wezterm?branch=PSUEDOCONSOLE_INHERIT_CURSOR#fe38df8409545a696909aa9a09e63438630f217d"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
dependencies = [
"libc",
"thiserror 1.0.69",
@@ -4656,7 +4662,8 @@ dependencies = [
[[package]]
name = "portable-pty"
version = "0.9.0"
source = "git+https://github.com/pakrym/wezterm?branch=PSUEDOCONSOLE_INHERIT_CURSOR#fe38df8409545a696909aa9a09e63438630f217d"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e"
dependencies = [
"anyhow",
"bitflags 1.3.2",
@@ -4665,7 +4672,7 @@ dependencies = [
"lazy_static",
"libc",
"log",
"nix 0.29.0",
"nix 0.28.0",
"serial2",
"shared_library",
"shell-words",

View File

@@ -289,7 +289,6 @@ opt-level = 0
# Uncomment to debug local changes.
# ratatui = { path = "../../ratatui" }
crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/color-query" }
portable-pty = { git = "https://github.com/pakrym/wezterm", branch = "PSUEDOCONSOLE_INHERIT_CURSOR" }
ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" }
# Uncomment to debug local changes.

View File

@@ -11,3 +11,19 @@ workspace = true
anyhow = { workspace = true }
portable-pty = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
[target.'cfg(windows)'.dependencies]
filedescriptor = "0.8.3"
lazy_static = { workspace = true }
log = { workspace = true }
shared_library = "0.1.9"
winapi = { version = "0.3.9", features = [
"handleapi",
"minwinbase",
"processthreadsapi",
"synchapi",
"winbase",
"wincon",
"winerror",
"winnt",
] }

View File

@@ -7,7 +7,11 @@ use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use std::time::Duration;
#[cfg(windows)]
mod win;
use anyhow::Result;
#[cfg(not(windows))]
use portable_pty::native_pty_system;
use portable_pty::CommandBuilder;
use portable_pty::MasterPty;
@@ -125,6 +129,16 @@ pub struct SpawnedPty {
pub exit_rx: oneshot::Receiver<i32>,
}
#[cfg(windows)]
fn platform_native_pty_system() -> Box<dyn portable_pty::PtySystem + Send> {
Box::new(win::ConPtySystem::default())
}
#[cfg(not(windows))]
fn platform_native_pty_system() -> Box<dyn portable_pty::PtySystem + Send> {
native_pty_system()
}
pub async fn spawn_pty_process(
program: &str,
args: &[String],
@@ -136,7 +150,7 @@ pub async fn spawn_pty_process(
anyhow::bail!("missing program for PTY spawn");
}
let pty_system = native_pty_system();
let pty_system = platform_native_pty_system();
let pair = pty_system.openpty(PtySize {
rows: 24,
cols: 80,

View File

@@ -0,0 +1,144 @@
#![allow(clippy::unwrap_used)]
// This file is copied from https://github.com/wezterm/wezterm (MIT license).
// Copyright (c) 2018-Present Wez Furlong
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::win::psuedocon::PsuedoCon;
use anyhow::Error;
use filedescriptor::FileDescriptor;
use filedescriptor::Pipe;
use portable_pty::cmdbuilder::CommandBuilder;
use portable_pty::Child;
use portable_pty::MasterPty;
use portable_pty::PtyPair;
use portable_pty::PtySize;
use portable_pty::PtySystem;
use portable_pty::SlavePty;
use std::sync::Arc;
use std::sync::Mutex;
use winapi::um::wincon::COORD;
#[derive(Default)]
pub struct ConPtySystem {}
impl PtySystem for ConPtySystem {
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
let stdin = Pipe::new()?;
let stdout = Pipe::new()?;
let con = PsuedoCon::new(
COORD {
X: size.cols as i16,
Y: size.rows as i16,
},
stdin.read,
stdout.write,
)?;
let master = ConPtyMasterPty {
inner: Arc::new(Mutex::new(Inner {
con,
readable: stdout.read,
writable: Some(stdin.write),
size,
})),
};
let slave = ConPtySlavePty {
inner: master.inner.clone(),
};
Ok(PtyPair {
master: Box::new(master),
slave: Box::new(slave),
})
}
}
struct Inner {
con: PsuedoCon,
readable: FileDescriptor,
writable: Option<FileDescriptor>,
size: PtySize,
}
impl Inner {
pub fn resize(
&mut self,
num_rows: u16,
num_cols: u16,
pixel_width: u16,
pixel_height: u16,
) -> Result<(), Error> {
self.con.resize(COORD {
X: num_cols as i16,
Y: num_rows as i16,
})?;
self.size = PtySize {
rows: num_rows,
cols: num_cols,
pixel_width,
pixel_height,
};
Ok(())
}
}
#[derive(Clone)]
pub struct ConPtyMasterPty {
inner: Arc<Mutex<Inner>>,
}
pub struct ConPtySlavePty {
inner: Arc<Mutex<Inner>>,
}
impl MasterPty for ConPtyMasterPty {
fn resize(&self, size: PtySize) -> anyhow::Result<()> {
let mut inner = self.inner.lock().unwrap();
inner.resize(size.rows, size.cols, size.pixel_width, size.pixel_height)
}
fn get_size(&self) -> Result<PtySize, Error> {
let inner = self.inner.lock().unwrap();
Ok(inner.size)
}
fn try_clone_reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
Ok(Box::new(self.inner.lock().unwrap().readable.try_clone()?))
}
fn take_writer(&self) -> anyhow::Result<Box<dyn std::io::Write + Send>> {
Ok(Box::new(
self.inner
.lock()
.unwrap()
.writable
.take()
.ok_or_else(|| anyhow::anyhow!("writer already taken"))?,
))
}
}
impl SlavePty for ConPtySlavePty {
fn spawn_command(&self, cmd: CommandBuilder) -> anyhow::Result<Box<dyn Child + Send + Sync>> {
let inner = self.inner.lock().unwrap();
let child = inner.con.spawn_command(cmd)?;
Ok(Box::new(child))
}
}

View File

@@ -0,0 +1,169 @@
#![allow(clippy::unwrap_used)]
// This file is copied from https://github.com/wezterm/wezterm (MIT license).
// Copyright (c) 2018-Present Wez Furlong
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use anyhow::Context as _;
use filedescriptor::OwnedHandle;
use portable_pty::Child;
use portable_pty::ChildKiller;
use portable_pty::ExitStatus;
use std::io::Error as IoError;
use std::io::Result as IoResult;
use std::os::windows::io::AsRawHandle;
use std::pin::Pin;
use std::sync::Mutex;
use std::task::Context;
use std::task::Poll;
use winapi::shared::minwindef::DWORD;
use winapi::um::minwinbase::STILL_ACTIVE;
use winapi::um::processthreadsapi::*;
use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::INFINITE;
pub mod conpty;
mod procthreadattr;
mod psuedocon;
pub use conpty::ConPtySystem;
#[derive(Debug)]
pub struct WinChild {
proc: Mutex<OwnedHandle>,
}
impl WinChild {
fn is_complete(&mut self) -> IoResult<Option<ExitStatus>> {
let mut status: DWORD = 0;
let proc = self.proc.lock().unwrap().try_clone().unwrap();
let res = unsafe { GetExitCodeProcess(proc.as_raw_handle() as _, &mut status) };
if res != 0 {
if status == STILL_ACTIVE {
Ok(None)
} else {
Ok(Some(ExitStatus::with_exit_code(status)))
}
} else {
Ok(None)
}
}
fn do_kill(&mut self) -> IoResult<()> {
let proc = self.proc.lock().unwrap().try_clone().unwrap();
let res = unsafe { TerminateProcess(proc.as_raw_handle() as _, 1) };
let err = IoError::last_os_error();
if res != 0 {
Err(err)
} else {
Ok(())
}
}
}
impl ChildKiller for WinChild {
fn kill(&mut self) -> IoResult<()> {
self.do_kill().ok();
Ok(())
}
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
let proc = self.proc.lock().unwrap().try_clone().unwrap();
Box::new(WinChildKiller { proc })
}
}
#[derive(Debug)]
pub struct WinChildKiller {
proc: OwnedHandle,
}
impl ChildKiller for WinChildKiller {
fn kill(&mut self) -> IoResult<()> {
let res = unsafe { TerminateProcess(self.proc.as_raw_handle() as _, 1) };
let err = IoError::last_os_error();
if res != 0 {
Err(err)
} else {
Ok(())
}
}
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
let proc = self.proc.try_clone().unwrap();
Box::new(WinChildKiller { proc })
}
}
impl Child for WinChild {
fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
self.is_complete()
}
fn wait(&mut self) -> IoResult<ExitStatus> {
if let Ok(Some(status)) = self.try_wait() {
return Ok(status);
}
let proc = self.proc.lock().unwrap().try_clone().unwrap();
unsafe {
WaitForSingleObject(proc.as_raw_handle() as _, INFINITE);
}
let mut status: DWORD = 0;
let res = unsafe { GetExitCodeProcess(proc.as_raw_handle() as _, &mut status) };
if res != 0 {
Ok(ExitStatus::with_exit_code(status))
} else {
Err(IoError::last_os_error())
}
}
fn process_id(&self) -> Option<u32> {
let res = unsafe { GetProcessId(self.proc.lock().unwrap().as_raw_handle() as _) };
if res == 0 {
None
} else {
Some(res)
}
}
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
let proc = self.proc.lock().unwrap();
Some(proc.as_raw_handle())
}
}
impl std::future::Future for WinChild {
type Output = anyhow::Result<ExitStatus>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<anyhow::Result<ExitStatus>> {
match self.is_complete() {
Ok(Some(status)) => Poll::Ready(Ok(status)),
Err(err) => Poll::Ready(Err(err).context("Failed to retrieve process exit status")),
Ok(None) => {
let proc = self.proc.lock().unwrap().try_clone()?;
let waker = cx.waker().clone();
std::thread::spawn(move || {
unsafe {
WaitForSingleObject(proc.as_raw_handle() as _, INFINITE);
}
waker.wake();
});
Poll::Pending
}
}
}
}

View File

@@ -0,0 +1,91 @@
#![allow(clippy::uninit_vec)]
// This file is copied from https://github.com/wezterm/wezterm (MIT license).
// Copyright (c) 2018-Present Wez Furlong
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use super::psuedocon::HPCON;
use anyhow::ensure;
use anyhow::Error;
use std::io::Error as IoError;
use std::mem;
use std::ptr;
use winapi::shared::minwindef::DWORD;
use winapi::um::processthreadsapi::*;
const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: usize = 0x00020016;
pub 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);
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()) };
}
}

View File

@@ -0,0 +1,322 @@
#![allow(clippy::expect_used)]
#![allow(clippy::upper_case_acronyms)]
// This file is copied from https://github.com/wezterm/wezterm (MIT license).
// Copyright (c) 2018-Present Wez Furlong
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use super::WinChild;
use crate::win::procthreadattr::ProcThreadAttributeList;
use anyhow::bail;
use anyhow::ensure;
use anyhow::Error;
use filedescriptor::FileDescriptor;
use filedescriptor::OwnedHandle;
use lazy_static::lazy_static;
use portable_pty::cmdbuilder::CommandBuilder;
use shared_library::shared_library;
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io::Error as IoError;
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
use std::os::windows::io::FromRawHandle;
use std::path::Path;
use std::ptr;
use std::sync::Mutex;
use winapi::shared::minwindef::DWORD;
use winapi::shared::winerror::HRESULT;
use winapi::shared::winerror::S_OK;
use winapi::um::handleapi::*;
use winapi::um::processthreadsapi::*;
use winapi::um::winbase::CREATE_UNICODE_ENVIRONMENT;
use winapi::um::winbase::EXTENDED_STARTUPINFO_PRESENT;
use winapi::um::winbase::STARTF_USESTDHANDLES;
use winapi::um::winbase::STARTUPINFOEXW;
use winapi::um::wincon::COORD;
use winapi::um::winnt::HANDLE;
pub type HPCON = HANDLE;
pub const PSEUDOCONSOLE_RESIZE_QUIRK: DWORD = 0x2;
#[allow(dead_code)]
pub const PSEUDOCONSOLE_PASSTHROUGH_MODE: DWORD = 0x8;
shared_library!(ConPtyFuncs,
pub fn CreatePseudoConsole(
size: COORD,
hInput: HANDLE,
hOutput: HANDLE,
flags: DWORD,
hpc: *mut HPCON
) -> HRESULT,
pub fn ResizePseudoConsole(hpc: HPCON, size: COORD) -> HRESULT,
pub fn ClosePseudoConsole(hpc: HPCON),
);
fn load_conpty() -> ConPtyFuncs {
let kernel = ConPtyFuncs::open(Path::new("kernel32.dll")).expect(
"this system does not support conpty. Windows 10 October 2018 or newer is required",
);
if let Ok(sideloaded) = ConPtyFuncs::open(Path::new("conpty.dll")) {
sideloaded
} else {
kernel
}
}
lazy_static! {
static ref CONPTY: ConPtyFuncs = load_conpty();
}
pub struct PsuedoCon {
con: HPCON,
}
unsafe impl Send for PsuedoCon {}
unsafe impl Sync for PsuedoCon {}
impl Drop for PsuedoCon {
fn drop(&mut self) {
unsafe { (CONPTY.ClosePseudoConsole)(self.con) };
}
}
impl PsuedoCon {
pub fn new(size: COORD, input: FileDescriptor, output: FileDescriptor) -> Result<Self, Error> {
let mut con: HPCON = INVALID_HANDLE_VALUE;
let result = unsafe {
(CONPTY.CreatePseudoConsole)(
size,
input.as_raw_handle() as _,
output.as_raw_handle() as _,
PSEUDOCONSOLE_RESIZE_QUIRK,
&mut con,
)
};
ensure!(
result == S_OK,
"failed to create psuedo console: HRESULT {result}"
);
Ok(Self { con })
}
pub fn resize(&self, size: COORD) -> Result<(), Error> {
let result = unsafe { (CONPTY.ResizePseudoConsole)(self.con, size) };
ensure!(
result == S_OK,
"failed to resize console to {}x{}: HRESULT: {}",
size.X,
size.Y,
result
);
Ok(())
}
pub fn spawn_command(&self, cmd: CommandBuilder) -> anyhow::Result<WinChild> {
let mut si: STARTUPINFOEXW = unsafe { mem::zeroed() };
si.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
si.StartupInfo.hStdInput = INVALID_HANDLE_VALUE;
si.StartupInfo.hStdOutput = INVALID_HANDLE_VALUE;
si.StartupInfo.hStdError = INVALID_HANDLE_VALUE;
let mut attrs = ProcThreadAttributeList::with_capacity(1)?;
attrs.set_pty(self.con)?;
si.lpAttributeList = attrs.as_mut_ptr();
let mut pi: PROCESS_INFORMATION = unsafe { mem::zeroed() };
let (mut exe, mut cmdline) = build_cmdline(&cmd)?;
let cmd_os = OsString::from_wide(&cmdline);
let cwd = resolve_current_directory(&cmd);
let mut env_block = build_environment_block(&cmd);
let res = unsafe {
CreateProcessW(
exe.as_mut_ptr(),
cmdline.as_mut_ptr(),
ptr::null_mut(),
ptr::null_mut(),
0,
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
env_block.as_mut_ptr() as *mut _,
cwd.as_ref().map_or(ptr::null(), std::vec::Vec::as_ptr),
&mut si.StartupInfo,
&mut pi,
)
};
if res == 0 {
let err = IoError::last_os_error();
let msg = format!(
"CreateProcessW `{:?}` in cwd `{:?}` failed: {}",
cmd_os,
cwd.as_ref().map(|c| OsString::from_wide(c)),
err
);
log::error!("{msg}");
bail!("{msg}");
}
let _main_thread = unsafe { OwnedHandle::from_raw_handle(pi.hThread as _) };
let proc = unsafe { OwnedHandle::from_raw_handle(pi.hProcess as _) };
Ok(WinChild {
proc: Mutex::new(proc),
})
}
}
fn resolve_current_directory(cmd: &CommandBuilder) -> Option<Vec<u16>> {
let home = cmd
.get_env("USERPROFILE")
.and_then(|path| Path::new(path).is_dir().then(|| path.to_owned()));
let cwd = cmd
.get_cwd()
.and_then(|path| Path::new(path).is_dir().then(|| path.to_owned()));
let dir = cwd.or(home)?;
let mut wide = Vec::new();
if Path::new(&dir).is_relative() {
if let Ok(current_dir) = env::current_dir() {
wide.extend(current_dir.join(&dir).as_os_str().encode_wide());
} else {
wide.extend(dir.encode_wide());
}
} else {
wide.extend(dir.encode_wide());
}
wide.push(0);
Some(wide)
}
fn build_environment_block(cmd: &CommandBuilder) -> Vec<u16> {
let mut block = Vec::new();
for (key, value) in cmd.iter_full_env_as_str() {
block.extend(OsStr::new(key).encode_wide());
block.push(b'=' as u16);
block.extend(OsStr::new(value).encode_wide());
block.push(0);
}
block.push(0);
block
}
fn build_cmdline(cmd: &CommandBuilder) -> anyhow::Result<(Vec<u16>, Vec<u16>)> {
let exe_os: OsString = if cmd.is_default_prog() {
cmd.get_env("ComSpec")
.unwrap_or(OsStr::new("cmd.exe"))
.to_os_string()
} else {
let argv = cmd.get_argv();
let Some(first) = argv.first() else {
anyhow::bail!("missing program name");
};
search_path(cmd, first)
};
let mut cmdline = Vec::new();
append_quoted(&exe_os, &mut cmdline);
for arg in cmd.get_argv().iter().skip(1) {
cmdline.push(' ' as u16);
ensure!(
!arg.encode_wide().any(|c| c == 0),
"invalid encoding for command line argument {arg:?}"
);
append_quoted(arg, &mut cmdline);
}
cmdline.push(0);
let mut exe: Vec<u16> = exe_os.encode_wide().collect();
exe.push(0);
Ok((exe, cmdline))
}
fn search_path(cmd: &CommandBuilder, exe: &OsStr) -> OsString {
if let Some(path) = cmd.get_env("PATH") {
let extensions = cmd.get_env("PATHEXT").unwrap_or(OsStr::new(".EXE"));
for path in env::split_paths(path) {
let candidate = path.join(exe);
if candidate.exists() {
return candidate.into_os_string();
}
for ext in env::split_paths(extensions) {
let ext = ext.to_str().unwrap_or("");
let path = path
.join(exe)
.with_extension(ext.strip_prefix('.').unwrap_or(ext));
if path.exists() {
return path.into_os_string();
}
}
}
}
exe.to_os_string()
}
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);
}

21
third_party/wezterm/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-Present Wez Furlong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.