use crate::Winsize;
use log::*;
use std::io::Error;
use std::os::windows::io::IntoRawHandle;
use std::{mem, ptr};
use crate::windows::pipes::{EventedAnonRead, EventedAnonWrite};
use windows_sys::core::{HRESULT, PWSTR};
use windows_sys::Win32::Foundation::{HANDLE, S_OK};
use windows_sys::Win32::System::Console::{
ClosePseudoConsole, CreatePseudoConsole, ResizePseudoConsole, COORD, HPCON,
};
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
use windows_sys::{s, w};
use windows_sys::Win32::System::Threading::{
CreateProcessW, InitializeProcThreadAttributeList, UpdateProcThreadAttribute,
EXTENDED_STARTUPINFO_PRESENT, PROCESS_INFORMATION,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, STARTF_USESTDHANDLES, STARTUPINFOEXW,
STARTUPINFOW,
};
use crate::windows::child::ChildExitWatcher;
use crate::windows::{cmdline, win32_string, Pty};
type CreatePseudoConsoleFn =
unsafe extern "system" fn(COORD, HANDLE, HANDLE, u32, *mut HPCON) -> HRESULT;
type ResizePseudoConsoleFn = unsafe extern "system" fn(HPCON, COORD) -> HRESULT;
type ClosePseudoConsoleFn = unsafe extern "system" fn(HPCON);
struct ConptyApi {
create: CreatePseudoConsoleFn,
resize: ResizePseudoConsoleFn,
close: ClosePseudoConsoleFn,
}
impl ConptyApi {
fn new() -> Self {
match Self::load_conpty() {
Some(conpty) => {
info!("Using conpty.dll for pseudoconsole");
conpty
}
None => {
info!("Using Windows API for pseudoconsole");
Self {
create: CreatePseudoConsole,
resize: ResizePseudoConsole,
close: ClosePseudoConsole,
}
}
}
}
fn load_conpty() -> Option<Self> {
type LoadedFn = unsafe extern "system" fn() -> isize;
unsafe {
let hmodule = LoadLibraryW(w!("conpty.dll"));
if hmodule == 0 {
return None;
}
let create_fn = GetProcAddress(hmodule, s!("CreatePseudoConsole"))?;
let resize_fn = GetProcAddress(hmodule, s!("ResizePseudoConsole"))?;
let close_fn = GetProcAddress(hmodule, s!("ClosePseudoConsole"))?;
Some(Self {
create: mem::transmute::<LoadedFn, CreatePseudoConsoleFn>(create_fn),
resize: mem::transmute::<LoadedFn, ResizePseudoConsoleFn>(resize_fn),
close: mem::transmute::<LoadedFn, ClosePseudoConsoleFn>(close_fn),
})
}
}
}
pub struct Conpty {
pub handle: HPCON,
api: ConptyApi,
}
impl Drop for Conpty {
fn drop(&mut self) {
unsafe { (self.api.close)(self.handle) }
}
}
unsafe impl Send for Conpty {}
pub fn new(shell: &str, columns: u16, rows: u16) -> Option<Pty> {
let api = ConptyApi::new();
let mut pty_handle: HPCON = 0;
let (conout, conout_pty_handle) = miow::pipe::anonymous(0).unwrap();
let (conin_pty_handle, conin) = miow::pipe::anonymous(0).unwrap();
let winsize = Winsize {
ws_row: rows as libc::c_ushort,
ws_col: columns as libc::c_ushort,
ws_width: 0 as libc::c_ushort,
ws_height: 0 as libc::c_ushort,
};
let result = unsafe {
(api.create)(
winsize.into(),
conin_pty_handle.into_raw_handle() as HANDLE,
conout_pty_handle.into_raw_handle() as HANDLE,
0,
&mut pty_handle as *mut _,
)
};
assert_eq!(result, S_OK);
let mut success;
let mut size: usize = 0;
let mut startup_info_ex: STARTUPINFOEXW = unsafe { mem::zeroed() };
startup_info_ex.StartupInfo.lpTitle = std::ptr::null_mut() as PWSTR;
startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
unsafe {
let failure = InitializeProcThreadAttributeList(
ptr::null_mut(),
1,
0,
&mut size as *mut usize,
) > 0;
if failure {
panic_shell_spawn();
}
}
let mut attr_list: Box<[u8]> = vec![0; size].into_boxed_slice();
#[allow(clippy::cast_ptr_alignment)]
{
startup_info_ex.lpAttributeList = attr_list.as_mut_ptr() as _;
}
unsafe {
success = InitializeProcThreadAttributeList(
startup_info_ex.lpAttributeList,
1,
0,
&mut size as *mut usize,
) > 0;
if !success {
panic_shell_spawn();
}
}
unsafe {
success = UpdateProcThreadAttribute(
startup_info_ex.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE as usize,
pty_handle as *mut std::ffi::c_void,
mem::size_of::<HPCON>(),
ptr::null_mut(),
ptr::null_mut(),
) > 0;
if !success {
panic_shell_spawn();
}
}
let cmdline = win32_string(&cmdline(shell));
let mut proc_info: PROCESS_INFORMATION = unsafe { mem::zeroed() };
unsafe {
success = CreateProcessW(
ptr::null(),
cmdline.as_ptr() as PWSTR,
ptr::null_mut(),
ptr::null_mut(),
false as i32,
EXTENDED_STARTUPINFO_PRESENT,
ptr::null_mut(),
ptr::null(),
&mut startup_info_ex.StartupInfo as *mut STARTUPINFOW,
&mut proc_info as *mut PROCESS_INFORMATION,
) > 0;
if !success {
panic_shell_spawn();
}
}
let conin = EventedAnonWrite::new(conin);
let conout = EventedAnonRead::new(conout);
let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap();
let conpty = Conpty {
handle: pty_handle as HPCON,
api,
};
Some(Pty::new(conpty, conout, conin, child_watcher))
}
fn panic_shell_spawn() {
panic!("Unable to spawn shell: {}", Error::last_os_error());
}
impl Conpty {
pub fn on_resize(&mut self, window_size: Winsize) {
let result = unsafe { (self.api.resize)(self.handle, window_size.into()) };
assert_eq!(result, S_OK);
}
}
impl From<Winsize> for COORD {
fn from(window_size: Winsize) -> Self {
let lines = window_size.ws_row;
let columns = window_size.ws_col;
COORD {
X: columns as i16,
Y: lines as i16,
}
}
}