mirror of
https://github.com/openai/codex.git
synced 2026-02-01 14:44:17 +00:00
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:
@@ -1 +1,2 @@
|
||||
iTerm
|
||||
psuedo
|
||||
13
codex-rs/Cargo.lock
generated
13
codex-rs/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
] }
|
||||
|
||||
@@ -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,
|
||||
|
||||
144
codex-rs/utils/pty/src/win/conpty.rs
Normal file
144
codex-rs/utils/pty/src/win/conpty.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
169
codex-rs/utils/pty/src/win/mod.rs
Normal file
169
codex-rs/utils/pty/src/win/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
codex-rs/utils/pty/src/win/procthreadattr.rs
Normal file
91
codex-rs/utils/pty/src/win/procthreadattr.rs
Normal 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()) };
|
||||
}
|
||||
}
|
||||
322
codex-rs/utils/pty/src/win/psuedocon.rs
Normal file
322
codex-rs/utils/pty/src/win/psuedocon.rs
Normal 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
21
third_party/wezterm/LICENSE
vendored
Normal 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.
|
||||
Reference in New Issue
Block a user