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
|
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"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"filedescriptor",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
|
"shared_library",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2578,7 +2583,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "filedescriptor"
|
name = "filedescriptor"
|
||||||
version = "0.8.3"
|
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 = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -4656,7 +4662,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-pty"
|
name = "portable-pty"
|
||||||
version = "0.9.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
@@ -4665,7 +4672,7 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"nix 0.29.0",
|
"nix 0.28.0",
|
||||||
"serial2",
|
"serial2",
|
||||||
"shared_library",
|
"shared_library",
|
||||||
"shell-words",
|
"shell-words",
|
||||||
|
|||||||
@@ -289,7 +289,6 @@ opt-level = 0
|
|||||||
# Uncomment to debug local changes.
|
# Uncomment to debug local changes.
|
||||||
# ratatui = { path = "../../ratatui" }
|
# ratatui = { path = "../../ratatui" }
|
||||||
crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/color-query" }
|
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" }
|
ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" }
|
||||||
|
|
||||||
# Uncomment to debug local changes.
|
# Uncomment to debug local changes.
|
||||||
|
|||||||
@@ -11,3 +11,19 @@ workspace = true
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
portable-pty = { workspace = true }
|
portable-pty = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
|
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::sync::Mutex as StdMutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod win;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
#[cfg(not(windows))]
|
||||||
use portable_pty::native_pty_system;
|
use portable_pty::native_pty_system;
|
||||||
use portable_pty::CommandBuilder;
|
use portable_pty::CommandBuilder;
|
||||||
use portable_pty::MasterPty;
|
use portable_pty::MasterPty;
|
||||||
@@ -125,6 +129,16 @@ pub struct SpawnedPty {
|
|||||||
pub exit_rx: oneshot::Receiver<i32>,
|
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(
|
pub async fn spawn_pty_process(
|
||||||
program: &str,
|
program: &str,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
@@ -136,7 +150,7 @@ pub async fn spawn_pty_process(
|
|||||||
anyhow::bail!("missing program for PTY spawn");
|
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 {
|
let pair = pty_system.openpty(PtySize {
|
||||||
rows: 24,
|
rows: 24,
|
||||||
cols: 80,
|
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